@herdctl/core 0.0.2 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (101) hide show
  1. package/LICENSE +21 -0
  2. package/dist/config/__tests__/agent.test.js +30 -0
  3. package/dist/config/__tests__/agent.test.js.map +1 -1
  4. package/dist/config/__tests__/merge.test.js +1 -1
  5. package/dist/config/__tests__/merge.test.js.map +1 -1
  6. package/dist/config/index.d.ts +1 -1
  7. package/dist/config/index.d.ts.map +1 -1
  8. package/dist/config/index.js +3 -1
  9. package/dist/config/index.js.map +1 -1
  10. package/dist/config/schema.d.ts +1005 -3
  11. package/dist/config/schema.d.ts.map +1 -1
  12. package/dist/config/schema.js +87 -4
  13. package/dist/config/schema.js.map +1 -1
  14. package/dist/fleet-manager/__tests__/coverage.test.js +6 -2
  15. package/dist/fleet-manager/__tests__/coverage.test.js.map +1 -1
  16. package/dist/fleet-manager/__tests__/integration.test.js +5 -0
  17. package/dist/fleet-manager/__tests__/integration.test.js.map +1 -1
  18. package/dist/fleet-manager/__tests__/job-control.test.js +13 -14
  19. package/dist/fleet-manager/__tests__/job-control.test.js.map +1 -1
  20. package/dist/fleet-manager/__tests__/reload.test.js +13 -3
  21. package/dist/fleet-manager/__tests__/reload.test.js.map +1 -1
  22. package/dist/fleet-manager/__tests__/status-queries.test.js +6 -0
  23. package/dist/fleet-manager/__tests__/status-queries.test.js.map +1 -1
  24. package/dist/fleet-manager/__tests__/trigger.test.js +10 -2
  25. package/dist/fleet-manager/__tests__/trigger.test.js.map +1 -1
  26. package/dist/fleet-manager/config-reload.d.ts +1 -1
  27. package/dist/fleet-manager/config-reload.js +1 -1
  28. package/dist/fleet-manager/fleet-manager.d.ts +1 -0
  29. package/dist/fleet-manager/fleet-manager.d.ts.map +1 -1
  30. package/dist/fleet-manager/fleet-manager.js +1 -0
  31. package/dist/fleet-manager/fleet-manager.js.map +1 -1
  32. package/dist/fleet-manager/job-control.d.ts +41 -0
  33. package/dist/fleet-manager/job-control.d.ts.map +1 -1
  34. package/dist/fleet-manager/job-control.js +243 -20
  35. package/dist/fleet-manager/job-control.js.map +1 -1
  36. package/dist/fleet-manager/schedule-executor.d.ts +20 -0
  37. package/dist/fleet-manager/schedule-executor.d.ts.map +1 -1
  38. package/dist/fleet-manager/schedule-executor.js +113 -3
  39. package/dist/fleet-manager/schedule-executor.js.map +1 -1
  40. package/dist/fleet-manager/types.d.ts +18 -0
  41. package/dist/fleet-manager/types.d.ts.map +1 -1
  42. package/dist/hooks/__tests__/discord-runner.test.d.ts +5 -0
  43. package/dist/hooks/__tests__/discord-runner.test.d.ts.map +1 -0
  44. package/dist/hooks/__tests__/discord-runner.test.js +606 -0
  45. package/dist/hooks/__tests__/discord-runner.test.js.map +1 -0
  46. package/dist/hooks/__tests__/hook-executor.test.d.ts +5 -0
  47. package/dist/hooks/__tests__/hook-executor.test.d.ts.map +1 -0
  48. package/dist/hooks/__tests__/hook-executor.test.js +443 -0
  49. package/dist/hooks/__tests__/hook-executor.test.js.map +1 -0
  50. package/dist/hooks/__tests__/shell-runner.test.d.ts +5 -0
  51. package/dist/hooks/__tests__/shell-runner.test.d.ts.map +1 -0
  52. package/dist/hooks/__tests__/shell-runner.test.js +201 -0
  53. package/dist/hooks/__tests__/shell-runner.test.js.map +1 -0
  54. package/dist/hooks/__tests__/webhook-runner.test.d.ts +5 -0
  55. package/dist/hooks/__tests__/webhook-runner.test.d.ts.map +1 -0
  56. package/dist/hooks/__tests__/webhook-runner.test.js +453 -0
  57. package/dist/hooks/__tests__/webhook-runner.test.js.map +1 -0
  58. package/dist/hooks/hook-executor.d.ts +129 -0
  59. package/dist/hooks/hook-executor.d.ts.map +1 -0
  60. package/dist/hooks/hook-executor.js +195 -0
  61. package/dist/hooks/hook-executor.js.map +1 -0
  62. package/dist/hooks/index.d.ts +15 -0
  63. package/dist/hooks/index.d.ts.map +1 -0
  64. package/dist/hooks/index.js +18 -0
  65. package/dist/hooks/index.js.map +1 -0
  66. package/dist/hooks/runners/discord.d.ts +66 -0
  67. package/dist/hooks/runners/discord.d.ts.map +1 -0
  68. package/dist/hooks/runners/discord.js +294 -0
  69. package/dist/hooks/runners/discord.js.map +1 -0
  70. package/dist/hooks/runners/shell.d.ts +71 -0
  71. package/dist/hooks/runners/shell.d.ts.map +1 -0
  72. package/dist/hooks/runners/shell.js +177 -0
  73. package/dist/hooks/runners/shell.js.map +1 -0
  74. package/dist/hooks/runners/webhook.d.ts +66 -0
  75. package/dist/hooks/runners/webhook.d.ts.map +1 -0
  76. package/dist/hooks/runners/webhook.js +163 -0
  77. package/dist/hooks/runners/webhook.js.map +1 -0
  78. package/dist/hooks/types.d.ts +196 -0
  79. package/dist/hooks/types.d.ts.map +1 -0
  80. package/dist/hooks/types.js +12 -0
  81. package/dist/hooks/types.js.map +1 -0
  82. package/dist/index.d.ts +1 -0
  83. package/dist/index.d.ts.map +1 -1
  84. package/dist/index.js +2 -0
  85. package/dist/index.js.map +1 -1
  86. package/dist/runner/__tests__/sdk-adapter.test.js +4 -3
  87. package/dist/runner/__tests__/sdk-adapter.test.js.map +1 -1
  88. package/dist/runner/message-processor.d.ts +5 -1
  89. package/dist/runner/message-processor.d.ts.map +1 -1
  90. package/dist/runner/message-processor.js +238 -18
  91. package/dist/runner/message-processor.js.map +1 -1
  92. package/dist/runner/sdk-adapter.d.ts.map +1 -1
  93. package/dist/runner/sdk-adapter.js +8 -1
  94. package/dist/runner/sdk-adapter.js.map +1 -1
  95. package/dist/runner/types.d.ts +23 -2
  96. package/dist/runner/types.d.ts.map +1 -1
  97. package/dist/scheduler/scheduler.d.ts.map +1 -1
  98. package/dist/scheduler/scheduler.js +9 -0
  99. package/dist/scheduler/scheduler.js.map +1 -1
  100. package/dist/state/schemas/job-metadata.d.ts +4 -4
  101. package/package.json +1 -1
@@ -0,0 +1,201 @@
1
+ /**
2
+ * Tests for ShellHookRunner
3
+ */
4
+ import { describe, it, expect, vi, beforeEach } from "vitest";
5
+ import { ShellHookRunner } from "../runners/shell.js";
6
+ describe("ShellHookRunner", () => {
7
+ // Create a mock logger
8
+ const mockLogger = {
9
+ debug: vi.fn(),
10
+ info: vi.fn(),
11
+ warn: vi.fn(),
12
+ error: vi.fn(),
13
+ };
14
+ // Create a sample hook context
15
+ const sampleContext = {
16
+ event: "completed",
17
+ job: {
18
+ id: "job-2024-01-15-abc123",
19
+ agentId: "test-agent",
20
+ scheduleName: "daily-run",
21
+ startedAt: "2024-01-15T10:00:00.000Z",
22
+ completedAt: "2024-01-15T10:05:00.000Z",
23
+ durationMs: 300000,
24
+ },
25
+ result: {
26
+ success: true,
27
+ output: "Job completed successfully",
28
+ },
29
+ agent: {
30
+ id: "test-agent",
31
+ name: "Test Agent",
32
+ },
33
+ };
34
+ beforeEach(() => {
35
+ vi.clearAllMocks();
36
+ });
37
+ describe("execute", () => {
38
+ it("should execute a simple shell command successfully", async () => {
39
+ const runner = new ShellHookRunner({ logger: mockLogger });
40
+ const config = {
41
+ type: "shell",
42
+ command: "echo 'test output'",
43
+ };
44
+ const result = await runner.execute(config, sampleContext);
45
+ expect(result.success).toBe(true);
46
+ expect(result.hookType).toBe("shell");
47
+ expect(result.exitCode).toBe(0);
48
+ expect(result.output).toBe("test output");
49
+ expect(result.durationMs).toBeGreaterThanOrEqual(0);
50
+ expect(mockLogger.info).toHaveBeenCalled();
51
+ });
52
+ it("should pass hook context as JSON on stdin", async () => {
53
+ const runner = new ShellHookRunner({ logger: mockLogger });
54
+ // Use cat to read stdin and output it
55
+ const config = {
56
+ type: "shell",
57
+ command: "cat",
58
+ };
59
+ const result = await runner.execute(config, sampleContext);
60
+ expect(result.success).toBe(true);
61
+ expect(result.output).toBeDefined();
62
+ // Parse the output to verify it's valid JSON matching our context
63
+ const parsedOutput = JSON.parse(result.output);
64
+ expect(parsedOutput.event).toBe("completed");
65
+ expect(parsedOutput.job.id).toBe("job-2024-01-15-abc123");
66
+ expect(parsedOutput.job.agentId).toBe("test-agent");
67
+ expect(parsedOutput.result.success).toBe(true);
68
+ });
69
+ it("should handle command failure with non-zero exit code", async () => {
70
+ const runner = new ShellHookRunner({ logger: mockLogger });
71
+ const config = {
72
+ type: "shell",
73
+ command: "exit 1",
74
+ };
75
+ const result = await runner.execute(config, sampleContext);
76
+ expect(result.success).toBe(false);
77
+ expect(result.exitCode).toBe(1);
78
+ expect(result.error).toContain("Exit code 1");
79
+ expect(mockLogger.warn).toHaveBeenCalled();
80
+ });
81
+ it("should capture stderr on failure", async () => {
82
+ const runner = new ShellHookRunner({ logger: mockLogger });
83
+ const config = {
84
+ type: "shell",
85
+ command: "echo 'error message' >&2 && exit 1",
86
+ };
87
+ const result = await runner.execute(config, sampleContext);
88
+ expect(result.success).toBe(false);
89
+ expect(result.error).toContain("error message");
90
+ });
91
+ it("should handle command not found", async () => {
92
+ const runner = new ShellHookRunner({ logger: mockLogger });
93
+ const config = {
94
+ type: "shell",
95
+ command: "nonexistent_command_xyz_123",
96
+ };
97
+ const result = await runner.execute(config, sampleContext);
98
+ expect(result.success).toBe(false);
99
+ expect(mockLogger.warn).toHaveBeenCalled();
100
+ });
101
+ // Skip: Flaky in CI - process signal handling varies across environments
102
+ it.skip("should respect timeout configuration", async () => {
103
+ const runner = new ShellHookRunner({ logger: mockLogger });
104
+ const config = {
105
+ type: "shell",
106
+ command: "node -e \"setTimeout(() => {}, 100000)\"",
107
+ timeout: 100, // 100ms timeout
108
+ };
109
+ const result = await runner.execute(config, sampleContext);
110
+ expect(result.success).toBe(false);
111
+ expect(result.error).toContain("timed out");
112
+ });
113
+ it("should use default timeout when not specified", async () => {
114
+ const runner = new ShellHookRunner({ logger: mockLogger });
115
+ const config = {
116
+ type: "shell",
117
+ command: "echo 'quick'",
118
+ // No timeout specified - should use default 30000ms
119
+ };
120
+ const result = await runner.execute(config, sampleContext);
121
+ expect(result.success).toBe(true);
122
+ });
123
+ it("should handle commands with pipes", async () => {
124
+ const runner = new ShellHookRunner({ logger: mockLogger });
125
+ const config = {
126
+ type: "shell",
127
+ command: "echo 'hello world' | tr '[:lower:]' '[:upper:]'",
128
+ };
129
+ const result = await runner.execute(config, sampleContext);
130
+ expect(result.success).toBe(true);
131
+ expect(result.output).toBe("HELLO WORLD");
132
+ });
133
+ it("should pass environment variables to the command", async () => {
134
+ const runner = new ShellHookRunner({
135
+ logger: mockLogger,
136
+ env: { MY_VAR: "test_value" },
137
+ });
138
+ const config = {
139
+ type: "shell",
140
+ command: "echo $MY_VAR",
141
+ };
142
+ const result = await runner.execute(config, sampleContext);
143
+ expect(result.success).toBe(true);
144
+ expect(result.output).toBe("test_value");
145
+ });
146
+ it("should handle failed event context", async () => {
147
+ const runner = new ShellHookRunner({ logger: mockLogger });
148
+ const failedContext = {
149
+ ...sampleContext,
150
+ event: "failed",
151
+ result: {
152
+ success: false,
153
+ output: "Job failed",
154
+ error: "Connection timeout",
155
+ },
156
+ };
157
+ const config = {
158
+ type: "shell",
159
+ command: "cat",
160
+ };
161
+ const result = await runner.execute(config, failedContext);
162
+ expect(result.success).toBe(true);
163
+ const parsedOutput = JSON.parse(result.output);
164
+ expect(parsedOutput.event).toBe("failed");
165
+ expect(parsedOutput.result.success).toBe(false);
166
+ expect(parsedOutput.result.error).toBe("Connection timeout");
167
+ });
168
+ it("should measure execution duration", async () => {
169
+ const runner = new ShellHookRunner({ logger: mockLogger });
170
+ const config = {
171
+ type: "shell",
172
+ command: "sleep 0.1",
173
+ };
174
+ const result = await runner.execute(config, sampleContext);
175
+ expect(result.success).toBe(true);
176
+ expect(result.durationMs).toBeGreaterThanOrEqual(80); // Allow some tolerance
177
+ expect(result.durationMs).toBeLessThan(5000); // Should not take too long
178
+ });
179
+ it("should work without a logger", async () => {
180
+ const runner = new ShellHookRunner(); // No options
181
+ const config = {
182
+ type: "shell",
183
+ command: "echo 'no logger'",
184
+ };
185
+ const result = await runner.execute(config, sampleContext);
186
+ expect(result.success).toBe(true);
187
+ expect(result.output).toBe("no logger");
188
+ });
189
+ it("should handle multiline output", async () => {
190
+ const runner = new ShellHookRunner({ logger: mockLogger });
191
+ const config = {
192
+ type: "shell",
193
+ command: "echo 'line1'; echo 'line2'; echo 'line3'",
194
+ };
195
+ const result = await runner.execute(config, sampleContext);
196
+ expect(result.success).toBe(true);
197
+ expect(result.output).toBe("line1\nline2\nline3");
198
+ });
199
+ });
200
+ });
201
+ //# sourceMappingURL=shell-runner.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shell-runner.test.js","sourceRoot":"","sources":["../../../src/hooks/__tests__/shell-runner.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Tests for WebhookHookRunner
3
+ */
4
+ export {};
5
+ //# sourceMappingURL=webhook-runner.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webhook-runner.test.d.ts","sourceRoot":"","sources":["../../../src/hooks/__tests__/webhook-runner.test.ts"],"names":[],"mappings":"AAAA;;GAEG"}
@@ -0,0 +1,453 @@
1
+ /**
2
+ * Tests for WebhookHookRunner
3
+ */
4
+ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
5
+ import { WebhookHookRunner } from "../runners/webhook.js";
6
+ describe("WebhookHookRunner", () => {
7
+ // Create a mock logger
8
+ const mockLogger = {
9
+ debug: vi.fn(),
10
+ info: vi.fn(),
11
+ warn: vi.fn(),
12
+ error: vi.fn(),
13
+ };
14
+ // Create a sample hook context
15
+ const sampleContext = {
16
+ event: "completed",
17
+ job: {
18
+ id: "job-2024-01-15-abc123",
19
+ agentId: "test-agent",
20
+ scheduleName: "daily-run",
21
+ startedAt: "2024-01-15T10:00:00.000Z",
22
+ completedAt: "2024-01-15T10:05:00.000Z",
23
+ durationMs: 300000,
24
+ },
25
+ result: {
26
+ success: true,
27
+ output: "Job completed successfully",
28
+ },
29
+ agent: {
30
+ id: "test-agent",
31
+ name: "Test Agent",
32
+ },
33
+ };
34
+ // Store original env
35
+ const originalEnv = { ...process.env };
36
+ beforeEach(() => {
37
+ vi.clearAllMocks();
38
+ // Reset env before each test
39
+ process.env = { ...originalEnv };
40
+ });
41
+ afterEach(() => {
42
+ // Restore original env
43
+ process.env = originalEnv;
44
+ });
45
+ describe("execute", () => {
46
+ it("should POST to the webhook URL successfully", async () => {
47
+ const mockFetch = vi.fn().mockResolvedValue({
48
+ ok: true,
49
+ status: 200,
50
+ statusText: "OK",
51
+ text: () => Promise.resolve('{"received": true}'),
52
+ });
53
+ const runner = new WebhookHookRunner({
54
+ logger: mockLogger,
55
+ fetch: mockFetch,
56
+ });
57
+ const config = {
58
+ type: "webhook",
59
+ url: "https://api.example.com/hooks/job-complete",
60
+ };
61
+ const result = await runner.execute(config, sampleContext);
62
+ expect(result.success).toBe(true);
63
+ expect(result.hookType).toBe("webhook");
64
+ expect(result.durationMs).toBeGreaterThanOrEqual(0);
65
+ expect(result.output).toBe('{"received": true}');
66
+ // Verify fetch was called correctly
67
+ expect(mockFetch).toHaveBeenCalledTimes(1);
68
+ const [url, options] = mockFetch.mock.calls[0];
69
+ expect(url).toBe("https://api.example.com/hooks/job-complete");
70
+ expect(options.method).toBe("POST");
71
+ expect(options.headers["Content-Type"]).toBe("application/json");
72
+ // Verify body is correct JSON
73
+ const body = JSON.parse(options.body);
74
+ expect(body.event).toBe("completed");
75
+ expect(body.job.id).toBe("job-2024-01-15-abc123");
76
+ expect(body.result.success).toBe(true);
77
+ expect(mockLogger.info).toHaveBeenCalled();
78
+ });
79
+ it("should use PUT method when configured", async () => {
80
+ const mockFetch = vi.fn().mockResolvedValue({
81
+ ok: true,
82
+ status: 200,
83
+ statusText: "OK",
84
+ text: () => Promise.resolve(""),
85
+ });
86
+ const runner = new WebhookHookRunner({
87
+ logger: mockLogger,
88
+ fetch: mockFetch,
89
+ });
90
+ const config = {
91
+ type: "webhook",
92
+ url: "https://api.example.com/hooks/update",
93
+ method: "PUT",
94
+ };
95
+ const result = await runner.execute(config, sampleContext);
96
+ expect(result.success).toBe(true);
97
+ const [, options] = mockFetch.mock.calls[0];
98
+ expect(options.method).toBe("PUT");
99
+ });
100
+ it("should include custom headers", async () => {
101
+ const mockFetch = vi.fn().mockResolvedValue({
102
+ ok: true,
103
+ status: 200,
104
+ statusText: "OK",
105
+ text: () => Promise.resolve(""),
106
+ });
107
+ const runner = new WebhookHookRunner({
108
+ logger: mockLogger,
109
+ fetch: mockFetch,
110
+ });
111
+ const config = {
112
+ type: "webhook",
113
+ url: "https://api.example.com/hooks",
114
+ headers: {
115
+ "X-Custom-Header": "custom-value",
116
+ Authorization: "Bearer static-token",
117
+ },
118
+ };
119
+ const result = await runner.execute(config, sampleContext);
120
+ expect(result.success).toBe(true);
121
+ const [, options] = mockFetch.mock.calls[0];
122
+ expect(options.headers["X-Custom-Header"]).toBe("custom-value");
123
+ expect(options.headers["Authorization"]).toBe("Bearer static-token");
124
+ expect(options.headers["Content-Type"]).toBe("application/json");
125
+ });
126
+ it("should substitute environment variables in headers", async () => {
127
+ process.env.API_TOKEN = "secret-token-123";
128
+ process.env.CUSTOM_VALUE = "env-custom-value";
129
+ const mockFetch = vi.fn().mockResolvedValue({
130
+ ok: true,
131
+ status: 200,
132
+ statusText: "OK",
133
+ text: () => Promise.resolve(""),
134
+ });
135
+ const runner = new WebhookHookRunner({
136
+ logger: mockLogger,
137
+ fetch: mockFetch,
138
+ });
139
+ const config = {
140
+ type: "webhook",
141
+ url: "https://api.example.com/hooks",
142
+ headers: {
143
+ Authorization: "Bearer ${API_TOKEN}",
144
+ "X-Custom": "${CUSTOM_VALUE}",
145
+ },
146
+ };
147
+ const result = await runner.execute(config, sampleContext);
148
+ expect(result.success).toBe(true);
149
+ const [, options] = mockFetch.mock.calls[0];
150
+ expect(options.headers["Authorization"]).toBe("Bearer secret-token-123");
151
+ expect(options.headers["X-Custom"]).toBe("env-custom-value");
152
+ });
153
+ it("should replace undefined env vars with empty string", async () => {
154
+ // Ensure the env var doesn't exist
155
+ delete process.env.UNDEFINED_VAR;
156
+ const mockFetch = vi.fn().mockResolvedValue({
157
+ ok: true,
158
+ status: 200,
159
+ statusText: "OK",
160
+ text: () => Promise.resolve(""),
161
+ });
162
+ const runner = new WebhookHookRunner({
163
+ logger: mockLogger,
164
+ fetch: mockFetch,
165
+ });
166
+ const config = {
167
+ type: "webhook",
168
+ url: "https://api.example.com/hooks",
169
+ headers: {
170
+ Authorization: "Bearer ${UNDEFINED_VAR}",
171
+ },
172
+ };
173
+ const result = await runner.execute(config, sampleContext);
174
+ expect(result.success).toBe(true);
175
+ const [, options] = mockFetch.mock.calls[0];
176
+ expect(options.headers["Authorization"]).toBe("Bearer ");
177
+ });
178
+ it("should handle multiple env var substitutions in one header", async () => {
179
+ process.env.USER = "testuser";
180
+ process.env.PASS = "testpass";
181
+ const mockFetch = vi.fn().mockResolvedValue({
182
+ ok: true,
183
+ status: 200,
184
+ statusText: "OK",
185
+ text: () => Promise.resolve(""),
186
+ });
187
+ const runner = new WebhookHookRunner({
188
+ logger: mockLogger,
189
+ fetch: mockFetch,
190
+ });
191
+ const config = {
192
+ type: "webhook",
193
+ url: "https://api.example.com/hooks",
194
+ headers: {
195
+ "X-Credentials": "${USER}:${PASS}",
196
+ },
197
+ };
198
+ const result = await runner.execute(config, sampleContext);
199
+ expect(result.success).toBe(true);
200
+ const [, options] = mockFetch.mock.calls[0];
201
+ expect(options.headers["X-Credentials"]).toBe("testuser:testpass");
202
+ });
203
+ it("should handle HTTP error responses", async () => {
204
+ const mockFetch = vi.fn().mockResolvedValue({
205
+ ok: false,
206
+ status: 500,
207
+ statusText: "Internal Server Error",
208
+ text: () => Promise.resolve("Something went wrong"),
209
+ });
210
+ const runner = new WebhookHookRunner({
211
+ logger: mockLogger,
212
+ fetch: mockFetch,
213
+ });
214
+ const config = {
215
+ type: "webhook",
216
+ url: "https://api.example.com/hooks",
217
+ };
218
+ const result = await runner.execute(config, sampleContext);
219
+ expect(result.success).toBe(false);
220
+ expect(result.hookType).toBe("webhook");
221
+ expect(result.error).toContain("HTTP 500");
222
+ expect(result.error).toContain("Internal Server Error");
223
+ expect(result.error).toContain("Something went wrong");
224
+ expect(result.output).toBe("Something went wrong");
225
+ expect(mockLogger.warn).toHaveBeenCalled();
226
+ });
227
+ it("should handle 4xx client errors", async () => {
228
+ const mockFetch = vi.fn().mockResolvedValue({
229
+ ok: false,
230
+ status: 401,
231
+ statusText: "Unauthorized",
232
+ text: () => Promise.resolve("Invalid token"),
233
+ });
234
+ const runner = new WebhookHookRunner({
235
+ logger: mockLogger,
236
+ fetch: mockFetch,
237
+ });
238
+ const config = {
239
+ type: "webhook",
240
+ url: "https://api.example.com/hooks",
241
+ };
242
+ const result = await runner.execute(config, sampleContext);
243
+ expect(result.success).toBe(false);
244
+ expect(result.error).toContain("HTTP 401");
245
+ expect(result.error).toContain("Unauthorized");
246
+ });
247
+ it("should handle network errors", async () => {
248
+ const mockFetch = vi.fn().mockRejectedValue(new Error("Network error: ECONNREFUSED"));
249
+ const runner = new WebhookHookRunner({
250
+ logger: mockLogger,
251
+ fetch: mockFetch,
252
+ });
253
+ const config = {
254
+ type: "webhook",
255
+ url: "https://api.example.com/hooks",
256
+ };
257
+ const result = await runner.execute(config, sampleContext);
258
+ expect(result.success).toBe(false);
259
+ expect(result.error).toContain("ECONNREFUSED");
260
+ expect(mockLogger.error).toHaveBeenCalled();
261
+ });
262
+ it("should handle timeout", async () => {
263
+ // Create a mock that simulates a timeout by aborting
264
+ const mockFetch = vi.fn().mockImplementation(async (_url, options) => {
265
+ // Wait for abort signal
266
+ return new Promise((_, reject) => {
267
+ options.signal.addEventListener("abort", () => {
268
+ const error = new Error("The operation was aborted");
269
+ error.name = "AbortError";
270
+ reject(error);
271
+ });
272
+ });
273
+ });
274
+ const runner = new WebhookHookRunner({
275
+ logger: mockLogger,
276
+ fetch: mockFetch,
277
+ });
278
+ const config = {
279
+ type: "webhook",
280
+ url: "https://api.example.com/hooks",
281
+ timeout: 50, // Very short timeout
282
+ };
283
+ const result = await runner.execute(config, sampleContext);
284
+ expect(result.success).toBe(false);
285
+ expect(result.error).toContain("timed out");
286
+ expect(result.error).toContain("50ms");
287
+ expect(mockLogger.error).toHaveBeenCalled();
288
+ });
289
+ it("should use default timeout when not specified", async () => {
290
+ const mockFetch = vi.fn().mockResolvedValue({
291
+ ok: true,
292
+ status: 200,
293
+ statusText: "OK",
294
+ text: () => Promise.resolve(""),
295
+ });
296
+ const runner = new WebhookHookRunner({
297
+ logger: mockLogger,
298
+ fetch: mockFetch,
299
+ });
300
+ const config = {
301
+ type: "webhook",
302
+ url: "https://api.example.com/hooks",
303
+ // No timeout specified - should use default 10000ms
304
+ };
305
+ const result = await runner.execute(config, sampleContext);
306
+ expect(result.success).toBe(true);
307
+ });
308
+ it("should accept 2xx status codes as success", async () => {
309
+ for (const status of [200, 201, 202, 204]) {
310
+ const mockFetch = vi.fn().mockResolvedValue({
311
+ ok: true,
312
+ status,
313
+ statusText: "OK",
314
+ text: () => Promise.resolve(""),
315
+ });
316
+ const runner = new WebhookHookRunner({
317
+ logger: mockLogger,
318
+ fetch: mockFetch,
319
+ });
320
+ const config = {
321
+ type: "webhook",
322
+ url: "https://api.example.com/hooks",
323
+ };
324
+ const result = await runner.execute(config, sampleContext);
325
+ expect(result.success).toBe(true);
326
+ }
327
+ });
328
+ it("should handle failed event context", async () => {
329
+ const mockFetch = vi.fn().mockResolvedValue({
330
+ ok: true,
331
+ status: 200,
332
+ statusText: "OK",
333
+ text: () => Promise.resolve(""),
334
+ });
335
+ const runner = new WebhookHookRunner({
336
+ logger: mockLogger,
337
+ fetch: mockFetch,
338
+ });
339
+ const failedContext = {
340
+ ...sampleContext,
341
+ event: "failed",
342
+ result: {
343
+ success: false,
344
+ output: "Job failed",
345
+ error: "Connection timeout",
346
+ },
347
+ };
348
+ const config = {
349
+ type: "webhook",
350
+ url: "https://api.example.com/hooks",
351
+ };
352
+ const result = await runner.execute(config, failedContext);
353
+ expect(result.success).toBe(true);
354
+ // Verify the failed context was sent in the body
355
+ const [, options] = mockFetch.mock.calls[0];
356
+ const body = JSON.parse(options.body);
357
+ expect(body.event).toBe("failed");
358
+ expect(body.result.success).toBe(false);
359
+ expect(body.result.error).toBe("Connection timeout");
360
+ });
361
+ it("should measure execution duration", async () => {
362
+ const mockFetch = vi.fn().mockImplementation(async () => {
363
+ // Add a small delay to measure
364
+ await new Promise((resolve) => setTimeout(resolve, 50));
365
+ return {
366
+ ok: true,
367
+ status: 200,
368
+ statusText: "OK",
369
+ text: () => Promise.resolve(""),
370
+ };
371
+ });
372
+ const runner = new WebhookHookRunner({
373
+ logger: mockLogger,
374
+ fetch: mockFetch,
375
+ });
376
+ const config = {
377
+ type: "webhook",
378
+ url: "https://api.example.com/hooks",
379
+ };
380
+ const result = await runner.execute(config, sampleContext);
381
+ expect(result.success).toBe(true);
382
+ expect(result.durationMs).toBeGreaterThanOrEqual(40); // Allow some tolerance
383
+ expect(result.durationMs).toBeLessThan(5000);
384
+ });
385
+ it("should work without a logger", async () => {
386
+ const mockFetch = vi.fn().mockResolvedValue({
387
+ ok: true,
388
+ status: 200,
389
+ statusText: "OK",
390
+ text: () => Promise.resolve("no logger"),
391
+ });
392
+ const runner = new WebhookHookRunner({ fetch: mockFetch }); // No logger
393
+ const config = {
394
+ type: "webhook",
395
+ url: "https://api.example.com/hooks",
396
+ };
397
+ const result = await runner.execute(config, sampleContext);
398
+ expect(result.success).toBe(true);
399
+ expect(result.output).toBe("no logger");
400
+ });
401
+ it("should handle response body read errors gracefully", async () => {
402
+ const mockFetch = vi.fn().mockResolvedValue({
403
+ ok: true,
404
+ status: 200,
405
+ statusText: "OK",
406
+ text: () => Promise.reject(new Error("Body read error")),
407
+ });
408
+ const runner = new WebhookHookRunner({
409
+ logger: mockLogger,
410
+ fetch: mockFetch,
411
+ });
412
+ const config = {
413
+ type: "webhook",
414
+ url: "https://api.example.com/hooks",
415
+ };
416
+ const result = await runner.execute(config, sampleContext);
417
+ expect(result.success).toBe(true);
418
+ expect(result.output).toBeUndefined();
419
+ });
420
+ it("should send complete HookContext in request body", async () => {
421
+ const mockFetch = vi.fn().mockResolvedValue({
422
+ ok: true,
423
+ status: 200,
424
+ statusText: "OK",
425
+ text: () => Promise.resolve(""),
426
+ });
427
+ const runner = new WebhookHookRunner({
428
+ logger: mockLogger,
429
+ fetch: mockFetch,
430
+ });
431
+ const config = {
432
+ type: "webhook",
433
+ url: "https://api.example.com/hooks",
434
+ };
435
+ await runner.execute(config, sampleContext);
436
+ const [, options] = mockFetch.mock.calls[0];
437
+ const body = JSON.parse(options.body);
438
+ // Verify all context fields are present
439
+ expect(body.event).toBe("completed");
440
+ expect(body.job.id).toBe("job-2024-01-15-abc123");
441
+ expect(body.job.agentId).toBe("test-agent");
442
+ expect(body.job.scheduleName).toBe("daily-run");
443
+ expect(body.job.startedAt).toBe("2024-01-15T10:00:00.000Z");
444
+ expect(body.job.completedAt).toBe("2024-01-15T10:05:00.000Z");
445
+ expect(body.job.durationMs).toBe(300000);
446
+ expect(body.result.success).toBe(true);
447
+ expect(body.result.output).toBe("Job completed successfully");
448
+ expect(body.agent.id).toBe("test-agent");
449
+ expect(body.agent.name).toBe("Test Agent");
450
+ });
451
+ });
452
+ });
453
+ //# sourceMappingURL=webhook-runner.test.js.map