@blokjs/api-call 2.0.1 → 2.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.
- package/package.json +3 -3
- package/dist/test/index.test.d.ts +0 -7
- package/dist/test/index.test.js +0 -110
- package/dist/test/util.test.d.ts +0 -5
- package/dist/test/util.test.js +0 -80
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@blokjs/api-call",
|
|
3
|
-
"version": "2.0
|
|
3
|
+
"version": "2.2.0",
|
|
4
4
|
"files": ["dist"],
|
|
5
5
|
"description": "Node module for making API calls",
|
|
6
6
|
"type": "module",
|
|
@@ -26,8 +26,8 @@
|
|
|
26
26
|
"vitest": "^4.0.18"
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"@blokjs/runner": "^2.0
|
|
30
|
-
"@blokjs/shared": "^2.0
|
|
29
|
+
"@blokjs/runner": "^2.1.0",
|
|
30
|
+
"@blokjs/shared": "^2.1.0",
|
|
31
31
|
"lodash": "^4.17.21",
|
|
32
32
|
"zod": "^3.24.2"
|
|
33
33
|
},
|
package/dist/test/index.test.js
DELETED
|
@@ -1,110 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* API Call Node Tests - Updated for Function-First Implementation
|
|
3
|
-
*
|
|
4
|
-
* Tests migrated from class-based to function-first pattern.
|
|
5
|
-
* All existing behavior is preserved.
|
|
6
|
-
*/
|
|
7
|
-
import { describe, expect, it, vi } from "vitest";
|
|
8
|
-
import ApiCallNode from "../index.js";
|
|
9
|
-
import { runApiCall } from "../util.js";
|
|
10
|
-
// Mock the util function
|
|
11
|
-
vi.mock("../util", () => ({
|
|
12
|
-
runApiCall: vi.fn(),
|
|
13
|
-
}));
|
|
14
|
-
describe("ApiCall Node - Function-First", () => {
|
|
15
|
-
const mockContext = {
|
|
16
|
-
id: "test-id",
|
|
17
|
-
workflow_name: "test-workflow",
|
|
18
|
-
workflow_path: "/test",
|
|
19
|
-
request: {
|
|
20
|
-
method: "POST",
|
|
21
|
-
body: { default: "data" },
|
|
22
|
-
headers: {},
|
|
23
|
-
params: {},
|
|
24
|
-
query: {},
|
|
25
|
-
},
|
|
26
|
-
response: {
|
|
27
|
-
data: {},
|
|
28
|
-
success: true,
|
|
29
|
-
error: null,
|
|
30
|
-
},
|
|
31
|
-
error: {
|
|
32
|
-
message: [],
|
|
33
|
-
},
|
|
34
|
-
vars: {},
|
|
35
|
-
config: {
|
|
36
|
-
"api-call": {}, // Node configuration
|
|
37
|
-
},
|
|
38
|
-
logger: {
|
|
39
|
-
log: vi.fn(),
|
|
40
|
-
info: vi.fn(),
|
|
41
|
-
error: vi.fn(),
|
|
42
|
-
warn: vi.fn(),
|
|
43
|
-
debug: vi.fn(),
|
|
44
|
-
},
|
|
45
|
-
env: {},
|
|
46
|
-
eventLogger: null,
|
|
47
|
-
_PRIVATE_: null,
|
|
48
|
-
};
|
|
49
|
-
const validInputs = {
|
|
50
|
-
method: "GET",
|
|
51
|
-
url: "https://api.example.com",
|
|
52
|
-
headers: { Authorization: "Bearer token" },
|
|
53
|
-
responseType: "json",
|
|
54
|
-
body: { key: "value" },
|
|
55
|
-
};
|
|
56
|
-
it("should successfully make an API call and return response", async () => {
|
|
57
|
-
const mockResult = { success: true, data: { message: "API Response" } };
|
|
58
|
-
// Mock the API call
|
|
59
|
-
vi.mocked(runApiCall).mockResolvedValue(mockResult);
|
|
60
|
-
// Execute the node using handle()
|
|
61
|
-
const result = (await ApiCallNode.handle(mockContext, validInputs));
|
|
62
|
-
// Check the result structure
|
|
63
|
-
expect(result.success).toBe(true);
|
|
64
|
-
expect(result.data).toEqual(mockResult);
|
|
65
|
-
expect(result.error).toBeNull();
|
|
66
|
-
});
|
|
67
|
-
it("should use ctx.response.data as the body if inputs.body is empty", async () => {
|
|
68
|
-
mockContext.response.data = { fallback: "data" };
|
|
69
|
-
const inputsWithoutBody = { ...validInputs, body: {} };
|
|
70
|
-
const mockResult = { success: true, data: { fallback: "data" } };
|
|
71
|
-
vi.mocked(runApiCall).mockResolvedValue(mockResult);
|
|
72
|
-
const result = (await ApiCallNode.handle(mockContext, inputsWithoutBody));
|
|
73
|
-
expect(result.success).toBe(true);
|
|
74
|
-
expect(result.data).toEqual(mockResult);
|
|
75
|
-
expect(result.error).toBeNull();
|
|
76
|
-
});
|
|
77
|
-
it("should return an error if the API call fails", async () => {
|
|
78
|
-
const mockError = new Error("API request failed");
|
|
79
|
-
vi.mocked(runApiCall).mockRejectedValue(mockError);
|
|
80
|
-
const result = (await ApiCallNode.handle(mockContext, validInputs));
|
|
81
|
-
expect(result.success).toBe(false);
|
|
82
|
-
expect(result.error).toBeDefined();
|
|
83
|
-
expect(result.error.message).toBe("API request failed");
|
|
84
|
-
expect(result.error.context.code).toBe(500); // Runtime error = 500
|
|
85
|
-
});
|
|
86
|
-
it("should validate input with Zod and reject invalid URLs", async () => {
|
|
87
|
-
const invalidInputs = {
|
|
88
|
-
...validInputs,
|
|
89
|
-
url: "not-a-valid-url",
|
|
90
|
-
};
|
|
91
|
-
const result = (await ApiCallNode.handle(mockContext, invalidInputs));
|
|
92
|
-
expect(result.success).toBe(false);
|
|
93
|
-
expect(result.error).toBeDefined();
|
|
94
|
-
expect(result.error.context.code).toBe(400); // Validation error = 400
|
|
95
|
-
});
|
|
96
|
-
it("should use default values for optional fields", async () => {
|
|
97
|
-
const minimalInputs = {
|
|
98
|
-
url: "https://api.example.com",
|
|
99
|
-
};
|
|
100
|
-
const mockResult = { success: true };
|
|
101
|
-
vi.mocked(runApiCall).mockResolvedValue(mockResult);
|
|
102
|
-
const result = (await ApiCallNode.handle(mockContext, minimalInputs));
|
|
103
|
-
expect(result.success).toBe(true);
|
|
104
|
-
// Verify runApiCall was called with defaults
|
|
105
|
-
expect(vi.mocked(runApiCall)).toHaveBeenCalledWith("https://api.example.com", "GET", // default
|
|
106
|
-
{}, // default headers
|
|
107
|
-
mockContext.response.data, // default body from context
|
|
108
|
-
"json");
|
|
109
|
-
});
|
|
110
|
-
});
|
package/dist/test/util.test.d.ts
DELETED
package/dist/test/util.test.js
DELETED
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tests for runApiCall's HTTP-error handling — it must NOT discard the upstream
|
|
3
|
-
* status, body, or Retry-After header on a >=400 response.
|
|
4
|
-
*/
|
|
5
|
-
import { GlobalError } from "@blokjs/shared";
|
|
6
|
-
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
7
|
-
import { runApiCall } from "../util.js";
|
|
8
|
-
function mockFetchResponse(opts) {
|
|
9
|
-
const headers = new Headers(opts.headers ?? {});
|
|
10
|
-
return {
|
|
11
|
-
status: opts.status,
|
|
12
|
-
statusText: opts.statusText ?? "",
|
|
13
|
-
ok: opts.status < 400,
|
|
14
|
-
headers,
|
|
15
|
-
json: async () => opts.json,
|
|
16
|
-
text: async () => opts.body ?? "",
|
|
17
|
-
};
|
|
18
|
-
}
|
|
19
|
-
describe("runApiCall — HTTP error handling", () => {
|
|
20
|
-
afterEach(() => {
|
|
21
|
-
vi.restoreAllMocks();
|
|
22
|
-
});
|
|
23
|
-
it("throws a GlobalError carrying the upstream status code on >=400 (not a generic 500)", async () => {
|
|
24
|
-
vi.stubGlobal("fetch", vi.fn(async () => mockFetchResponse({
|
|
25
|
-
status: 429,
|
|
26
|
-
statusText: "Too Many Requests",
|
|
27
|
-
headers: { "content-type": "application/json", "retry-after": "30" },
|
|
28
|
-
json: { error: "rate_limited" },
|
|
29
|
-
})));
|
|
30
|
-
const err = await runApiCall("https://api.example.com/x", "GET", {}, {}, "json").catch((e) => e);
|
|
31
|
-
expect(err).toBeInstanceOf(GlobalError);
|
|
32
|
-
const ge = err;
|
|
33
|
-
expect(ge.context.code).toBe(429);
|
|
34
|
-
expect(ge.context.name).toBe("ApiCallError");
|
|
35
|
-
expect(ge.context.json).toMatchObject({
|
|
36
|
-
status: 429,
|
|
37
|
-
statusText: "Too Many Requests",
|
|
38
|
-
retryAfter: "30",
|
|
39
|
-
retryAfterSeconds: 30,
|
|
40
|
-
body: { error: "rate_limited" },
|
|
41
|
-
});
|
|
42
|
-
});
|
|
43
|
-
it("captures a text body and an HTTP-date Retry-After", async () => {
|
|
44
|
-
const future = new Date(Date.now() + 60_000).toUTCString();
|
|
45
|
-
vi.stubGlobal("fetch", vi.fn(async () => mockFetchResponse({
|
|
46
|
-
status: 503,
|
|
47
|
-
statusText: "Service Unavailable",
|
|
48
|
-
headers: { "content-type": "text/plain", "retry-after": future },
|
|
49
|
-
body: "down for maintenance",
|
|
50
|
-
})));
|
|
51
|
-
const err = (await runApiCall("https://api.example.com/y", "GET", {}, {}, "json").catch((e) => e));
|
|
52
|
-
expect(err.context.code).toBe(503);
|
|
53
|
-
const json = err.context.json;
|
|
54
|
-
expect(json.body).toBe("down for maintenance");
|
|
55
|
-
expect(typeof json.retryAfterSeconds).toBe("number");
|
|
56
|
-
expect(json.retryAfterSeconds).toBeGreaterThan(50);
|
|
57
|
-
});
|
|
58
|
-
it("omits retry fields when no Retry-After header is present", async () => {
|
|
59
|
-
vi.stubGlobal("fetch", vi.fn(async () => mockFetchResponse({
|
|
60
|
-
status: 404,
|
|
61
|
-
statusText: "Not Found",
|
|
62
|
-
headers: { "content-type": "application/json" },
|
|
63
|
-
json: { message: "missing" },
|
|
64
|
-
})));
|
|
65
|
-
const err = (await runApiCall("https://api.example.com/z", "GET", {}, {}, "json").catch((e) => e));
|
|
66
|
-
expect(err.context.code).toBe(404);
|
|
67
|
-
const json = err.context.json;
|
|
68
|
-
expect(json).not.toHaveProperty("retryAfter");
|
|
69
|
-
expect(json.body).toMatchObject({ message: "missing" });
|
|
70
|
-
});
|
|
71
|
-
it("returns the parsed JSON body on a 2xx response", async () => {
|
|
72
|
-
vi.stubGlobal("fetch", vi.fn(async () => mockFetchResponse({
|
|
73
|
-
status: 200,
|
|
74
|
-
headers: { "content-type": "application/json" },
|
|
75
|
-
json: { ok: true },
|
|
76
|
-
})));
|
|
77
|
-
const result = await runApiCall("https://api.example.com/ok", "GET", {}, {}, "json");
|
|
78
|
-
expect(result).toEqual({ ok: true });
|
|
79
|
-
});
|
|
80
|
-
});
|