@inductiv/node-red-openai-api 1.103.0 → 6.27.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/README.md +213 -86
- package/examples/realtime/client-secrets.json +182 -0
- package/examples/responses/computer-use.json +142 -0
- package/examples/responses/mcp.json +1 -1
- package/examples/responses/phase.json +102 -0
- package/examples/responses/tool-search.json +107 -0
- package/examples/responses/websocket.json +172 -0
- package/internals/openai-api-features-v6.23.0-v6.27.0.md +96 -0
- package/lib.js +12696 -15003
- package/locales/en-US/node.json +50 -1
- package/node.html +1723 -1012
- package/node.js +204 -54
- package/package.json +9 -7
- package/src/assistants/help.html +1 -77
- package/src/audio/help.html +1 -37
- package/src/batch/help.html +3 -17
- package/src/chat/help.html +11 -89
- package/src/container-files/help.html +1 -27
- package/src/containers/help.html +8 -18
- package/src/conversations/help.html +135 -0
- package/src/conversations/methods.js +73 -0
- package/src/conversations/template.html +10 -0
- package/src/embeddings/help.html +1 -11
- package/src/evals/help.html +249 -0
- package/src/evals/methods.js +114 -0
- package/src/evals/template.html +14 -0
- package/src/files/help.html +4 -17
- package/src/fine-tuning/help.html +1 -35
- package/src/images/help.html +1 -45
- package/src/lib.js +53 -1
- package/src/messages/help.html +19 -39
- package/src/messages/methods.js +13 -0
- package/src/messages/template.html +7 -18
- package/src/models/help.html +1 -5
- package/src/moderations/help.html +1 -5
- package/src/node.html +126 -37
- package/src/realtime/help.html +209 -0
- package/src/realtime/methods.js +45 -0
- package/src/realtime/template.html +7 -0
- package/src/responses/help.html +286 -63
- package/src/responses/methods.js +234 -16
- package/src/responses/template.html +21 -1
- package/src/responses/websocket.js +150 -0
- package/src/runs/help.html +1 -123
- package/src/skills/help.html +183 -0
- package/src/skills/methods.js +99 -0
- package/src/skills/template.html +13 -0
- package/src/threads/help.html +1 -15
- package/src/uploads/help.html +1 -21
- package/src/vector-store-file-batches/help.html +1 -27
- package/src/vector-store-file-batches/methods.js +5 -5
- package/src/vector-store-files/help.html +1 -25
- package/src/vector-store-files/methods.js +4 -7
- package/src/vector-stores/help.html +2 -31
- package/src/vector-stores/methods.js +5 -11
- package/src/vector-stores/template.html +7 -22
- package/src/videos/help.html +113 -0
- package/src/videos/methods.js +50 -0
- package/src/videos/template.html +8 -0
- package/src/webhooks/help.html +61 -0
- package/src/webhooks/methods.js +40 -0
- package/src/webhooks/template.html +4 -0
- package/test/openai-methods-mapping.test.js +1559 -0
- package/test/openai-node-auth-routing.test.js +206 -0
- package/test/openai-responses-websocket.test.js +472 -0
- package/test/service-host-editor-template.test.js +56 -0
- package/test/service-host-node.test.js +185 -0
- package/test/services.test.js +150 -0
- package/test/utils.test.js +78 -0
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
// This file is about the editor-side Service Host experience.
|
|
4
|
+
// It checks that the config UI still exposes the typed-input behavior we rely on for API keys and related fields.
|
|
5
|
+
|
|
6
|
+
const assert = require("node:assert/strict");
|
|
7
|
+
const fs = require("node:fs");
|
|
8
|
+
const path = require("node:path");
|
|
9
|
+
const test = require("node:test");
|
|
10
|
+
|
|
11
|
+
const templatePath = path.join(__dirname, "..", "src", "node.html");
|
|
12
|
+
const template = fs.readFileSync(templatePath, "utf8");
|
|
13
|
+
|
|
14
|
+
test("service host API key editor uses native typedInput cred handling", () => {
|
|
15
|
+
assert.match(
|
|
16
|
+
template,
|
|
17
|
+
/types: \["cred", "env", "msg", "flow", "global"\],/
|
|
18
|
+
);
|
|
19
|
+
assert.match(
|
|
20
|
+
template,
|
|
21
|
+
/<input type="text" id="node-config-input-secureApiKeyValue" placeholder="" \/>/
|
|
22
|
+
);
|
|
23
|
+
assert.match(
|
|
24
|
+
template,
|
|
25
|
+
/<input\s+type="text"\s+id="node-config-input-secureApiKeyHeaderOrQueryName"\s+placeholder="Authorization"\s+\/>/
|
|
26
|
+
);
|
|
27
|
+
assert.match(
|
|
28
|
+
template,
|
|
29
|
+
/if \(inputValue && inputValue !== "__PWRD__"\) \{/
|
|
30
|
+
);
|
|
31
|
+
assert.match(
|
|
32
|
+
template,
|
|
33
|
+
/const syncApiKeyInputValue = function \(\) \{/
|
|
34
|
+
);
|
|
35
|
+
assert.match(
|
|
36
|
+
template,
|
|
37
|
+
/if \(selectedType !== "cred" && currentValue === "__PWRD__"\) \{/
|
|
38
|
+
);
|
|
39
|
+
assert.match(
|
|
40
|
+
template,
|
|
41
|
+
/apiKeyInput\.typedInput\("value", apiKeyRef\.val\(\) \|\| ""\);/
|
|
42
|
+
);
|
|
43
|
+
assert.match(
|
|
44
|
+
template,
|
|
45
|
+
/if \(selectedType === "cred" && previousApiKeyType !== "cred"\) \{/
|
|
46
|
+
);
|
|
47
|
+
assert.match(
|
|
48
|
+
template,
|
|
49
|
+
/const inputValue = apiKeyInput\.typedInput\("value"\);/
|
|
50
|
+
);
|
|
51
|
+
assert.match(
|
|
52
|
+
template,
|
|
53
|
+
/const existingCredValue = apiKeyInput\.val\(\);/
|
|
54
|
+
);
|
|
55
|
+
assert.ok(!/apiKeyInput\.prop\("type"/.test(template));
|
|
56
|
+
});
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
// This file covers the runtime side of the Service Host config node.
|
|
4
|
+
// It makes sure typed values resolve the right way, including the older storage formats we still support.
|
|
5
|
+
|
|
6
|
+
const assert = require("node:assert/strict");
|
|
7
|
+
const test = require("node:test");
|
|
8
|
+
|
|
9
|
+
const nodeModule = require("../node.js");
|
|
10
|
+
|
|
11
|
+
function createServiceHostNode(evaluateNodeProperty) {
|
|
12
|
+
let ServiceHostNode;
|
|
13
|
+
|
|
14
|
+
const RED = {
|
|
15
|
+
nodes: {
|
|
16
|
+
createNode: (node, config) => {
|
|
17
|
+
node.credentials = config.credentials || {};
|
|
18
|
+
},
|
|
19
|
+
registerType: (name, ctor) => {
|
|
20
|
+
if (name === "Service Host") {
|
|
21
|
+
ServiceHostNode = ctor;
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
getNode: () => undefined,
|
|
25
|
+
},
|
|
26
|
+
util: {
|
|
27
|
+
evaluateNodeProperty,
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
nodeModule(RED);
|
|
32
|
+
assert.ok(ServiceHostNode, "Service Host constructor should register");
|
|
33
|
+
return ServiceHostNode;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function createEvaluateNodeProperty(calls, options = {}) {
|
|
37
|
+
return (value, type, node, msg, callback) => {
|
|
38
|
+
calls.push({ value, type, node, msg, hasCallback: typeof callback === "function" });
|
|
39
|
+
|
|
40
|
+
if (type === "flow" || type === "global") {
|
|
41
|
+
const resolvedValue = `resolved:${type}:${value}`;
|
|
42
|
+
if (typeof callback === "function") {
|
|
43
|
+
const deliver = options.asyncFlowGlobal ? setImmediate : (fn) => fn();
|
|
44
|
+
deliver(() => callback(null, resolvedValue));
|
|
45
|
+
return undefined;
|
|
46
|
+
}
|
|
47
|
+
return resolvedValue;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (type === "str" || type === "cred") {
|
|
51
|
+
if (typeof callback === "function") {
|
|
52
|
+
callback(null, value);
|
|
53
|
+
return undefined;
|
|
54
|
+
}
|
|
55
|
+
return value;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const resolvedValue = `resolved:${type}:${value}`;
|
|
59
|
+
if (typeof callback === "function") {
|
|
60
|
+
callback(null, resolvedValue);
|
|
61
|
+
return undefined;
|
|
62
|
+
}
|
|
63
|
+
return resolvedValue;
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
test("uses credential value when API key type is cred", () => {
|
|
68
|
+
const calls = [];
|
|
69
|
+
const ServiceHostNode = createServiceHostNode(createEvaluateNodeProperty(calls));
|
|
70
|
+
const node = new ServiceHostNode({
|
|
71
|
+
secureApiKeyValueType: "cred",
|
|
72
|
+
secureApiKeyValueRef: "",
|
|
73
|
+
credentials: { secureApiKeyValue: "sk-test" },
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
const result = node.evaluateTyped("secureApiKeyValue", {}, node);
|
|
77
|
+
|
|
78
|
+
assert.equal(result, "sk-test");
|
|
79
|
+
assert.equal(calls.length, 1);
|
|
80
|
+
assert.equal(calls[0].value, "sk-test");
|
|
81
|
+
assert.equal(calls[0].type, "cred");
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
test("uses explicit reference value when API key type is env", () => {
|
|
85
|
+
const calls = [];
|
|
86
|
+
const ServiceHostNode = createServiceHostNode(createEvaluateNodeProperty(calls));
|
|
87
|
+
const node = new ServiceHostNode({
|
|
88
|
+
secureApiKeyValueType: "env",
|
|
89
|
+
secureApiKeyValueRef: "OPENAI_API_KEY",
|
|
90
|
+
credentials: { secureApiKeyValue: "sk-ignored" },
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
const result = node.evaluateTyped("secureApiKeyValue", {}, node);
|
|
94
|
+
|
|
95
|
+
assert.equal(result, "resolved:env:OPENAI_API_KEY");
|
|
96
|
+
assert.equal(calls.length, 1);
|
|
97
|
+
assert.equal(calls[0].value, "OPENAI_API_KEY");
|
|
98
|
+
assert.equal(calls[0].type, "env");
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
test("supports legacy non-cred nodes that stored env reference in credentials", () => {
|
|
102
|
+
const calls = [];
|
|
103
|
+
const ServiceHostNode = createServiceHostNode(createEvaluateNodeProperty(calls));
|
|
104
|
+
const node = new ServiceHostNode({
|
|
105
|
+
secureApiKeyValueType: "env",
|
|
106
|
+
credentials: { secureApiKeyValue: "OPENAI_API_KEY" },
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
const result = node.evaluateTyped("secureApiKeyValue", {}, node);
|
|
110
|
+
|
|
111
|
+
assert.equal(result, "resolved:env:OPENAI_API_KEY");
|
|
112
|
+
assert.equal(calls.length, 1);
|
|
113
|
+
assert.equal(calls[0].value, "OPENAI_API_KEY");
|
|
114
|
+
assert.equal(calls[0].type, "env");
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
test("falls back to credential value when non-cred reference is empty", () => {
|
|
118
|
+
const calls = [];
|
|
119
|
+
const ServiceHostNode = createServiceHostNode(createEvaluateNodeProperty(calls));
|
|
120
|
+
const node = new ServiceHostNode({
|
|
121
|
+
secureApiKeyValueType: "env",
|
|
122
|
+
secureApiKeyValueRef: "",
|
|
123
|
+
credentials: { secureApiKeyValue: "OPENAI_API_KEY" },
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
const result = node.evaluateTyped("secureApiKeyValue", {}, node);
|
|
127
|
+
|
|
128
|
+
assert.equal(result, "resolved:env:OPENAI_API_KEY");
|
|
129
|
+
assert.equal(calls.length, 1);
|
|
130
|
+
assert.equal(calls[0].value, "OPENAI_API_KEY");
|
|
131
|
+
assert.equal(calls[0].type, "env");
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
test("supports legacy typed value stored only in credentials type field", () => {
|
|
135
|
+
const calls = [];
|
|
136
|
+
const ServiceHostNode = createServiceHostNode(createEvaluateNodeProperty(calls));
|
|
137
|
+
const node = new ServiceHostNode({
|
|
138
|
+
credentials: {
|
|
139
|
+
secureApiKeyValue: "OPENAI_API_KEY",
|
|
140
|
+
secureApiKeyValueType: "env",
|
|
141
|
+
},
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
const result = node.evaluateTyped("secureApiKeyValue", {}, node);
|
|
145
|
+
|
|
146
|
+
assert.equal(result, "resolved:env:OPENAI_API_KEY");
|
|
147
|
+
assert.equal(calls.length, 1);
|
|
148
|
+
assert.equal(calls[0].value, "OPENAI_API_KEY");
|
|
149
|
+
assert.equal(calls[0].type, "env");
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
test("supports legacy string API key values without reference field", () => {
|
|
153
|
+
const calls = [];
|
|
154
|
+
const ServiceHostNode = createServiceHostNode(createEvaluateNodeProperty(calls));
|
|
155
|
+
const node = new ServiceHostNode({
|
|
156
|
+
secureApiKeyValueType: "str",
|
|
157
|
+
credentials: { secureApiKeyValue: "sk-legacy" },
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
const result = node.evaluateTyped("secureApiKeyValue", {}, node);
|
|
161
|
+
|
|
162
|
+
assert.equal(result, "sk-legacy");
|
|
163
|
+
assert.equal(calls.length, 1);
|
|
164
|
+
assert.equal(calls[0].value, "sk-legacy");
|
|
165
|
+
assert.equal(calls[0].type, "str");
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
test("evaluateTypedAsync resolves flow/global types using callback path", async () => {
|
|
169
|
+
const calls = [];
|
|
170
|
+
const ServiceHostNode = createServiceHostNode(
|
|
171
|
+
createEvaluateNodeProperty(calls, { asyncFlowGlobal: true })
|
|
172
|
+
);
|
|
173
|
+
const node = new ServiceHostNode({
|
|
174
|
+
secureApiKeyValueType: "flow",
|
|
175
|
+
secureApiKeyValueRef: "openai.key",
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
const result = await node.evaluateTypedAsync("secureApiKeyValue", {}, node);
|
|
179
|
+
|
|
180
|
+
assert.equal(result, "resolved:flow:openai.key");
|
|
181
|
+
assert.equal(calls.length, 1);
|
|
182
|
+
assert.equal(calls[0].value, "openai.key");
|
|
183
|
+
assert.equal(calls[0].type, "flow");
|
|
184
|
+
assert.equal(calls[0].hasCallback, true);
|
|
185
|
+
});
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
// This is a legacy high-level service test file.
|
|
4
|
+
// It exercises the older service factory layer and mocked API client behavior in a more end-to-end style.
|
|
5
|
+
|
|
6
|
+
const assert = require("assert");
|
|
7
|
+
const { createClient } = require("../src/client");
|
|
8
|
+
const { createApi } = require("../src/index");
|
|
9
|
+
|
|
10
|
+
// Mock the OpenAI API client
|
|
11
|
+
jest.mock("../src/client", () => {
|
|
12
|
+
return {
|
|
13
|
+
createClient: jest.fn().mockReturnValue({
|
|
14
|
+
chat: {
|
|
15
|
+
completions: {
|
|
16
|
+
create: jest.fn().mockResolvedValue({ id: "chat-123", choices: [] }),
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
beta: {
|
|
20
|
+
threads: {
|
|
21
|
+
create: jest.fn().mockResolvedValue({ id: "thread-123" }),
|
|
22
|
+
messages: {
|
|
23
|
+
create: jest.fn().mockResolvedValue({ id: "message-123" }),
|
|
24
|
+
list: jest
|
|
25
|
+
.fn()
|
|
26
|
+
.mockResolvedValue({ data: [{ id: "message-123" }] }),
|
|
27
|
+
retrieve: jest.fn().mockResolvedValue({ id: "message-123" }),
|
|
28
|
+
update: jest
|
|
29
|
+
.fn()
|
|
30
|
+
.mockResolvedValue({ id: "message-123", updated: true }),
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
batches: {
|
|
35
|
+
create: jest.fn().mockResolvedValue({ id: "batch-123" }),
|
|
36
|
+
retrieve: jest
|
|
37
|
+
.fn()
|
|
38
|
+
.mockResolvedValue({ id: "batch-123", status: "pending" }),
|
|
39
|
+
list: jest.fn().mockResolvedValue({ data: [{ id: "batch-123" }] }),
|
|
40
|
+
cancel: jest
|
|
41
|
+
.fn()
|
|
42
|
+
.mockResolvedValue({ id: "batch-123", cancelled: true }),
|
|
43
|
+
},
|
|
44
|
+
}),
|
|
45
|
+
};
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
describe("API Services", () => {
|
|
49
|
+
beforeEach(() => {
|
|
50
|
+
jest.clearAllMocks();
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
describe("ChatService", () => {
|
|
54
|
+
it("should create chat completion", async () => {
|
|
55
|
+
const api = createApi({ apiKey: "test-key" });
|
|
56
|
+
const result = await api.chat.createChatCompletion({
|
|
57
|
+
payload: { messages: [{ role: "user", content: "Hello" }] },
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
assert.strictEqual(result.id, "chat-123");
|
|
61
|
+
expect(createClient).toHaveBeenCalledWith({ apiKey: "test-key" });
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
describe("MessagesService", () => {
|
|
66
|
+
it("should create a message", async () => {
|
|
67
|
+
const api = createApi({ apiKey: "test-key" });
|
|
68
|
+
const result = await api.messages.createMessage({
|
|
69
|
+
payload: { thread_id: "thread-123", content: "Hello" },
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
assert.strictEqual(result.id, "message-123");
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it("should list messages", async () => {
|
|
76
|
+
const api = createApi({ apiKey: "test-key" });
|
|
77
|
+
const result = await api.messages.listMessages({
|
|
78
|
+
payload: { thread_id: "thread-123" },
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
assert.strictEqual(result.length, 1);
|
|
82
|
+
assert.strictEqual(result[0].id, "message-123");
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it("should get a message", async () => {
|
|
86
|
+
const api = createApi({ apiKey: "test-key" });
|
|
87
|
+
const result = await api.messages.getMessage({
|
|
88
|
+
payload: { thread_id: "thread-123", message_id: "message-123" },
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
assert.strictEqual(result.id, "message-123");
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it("should modify a message", async () => {
|
|
95
|
+
const api = createApi({ apiKey: "test-key" });
|
|
96
|
+
const result = await api.messages.modifyMessage({
|
|
97
|
+
payload: {
|
|
98
|
+
thread_id: "thread-123",
|
|
99
|
+
message_id: "message-123",
|
|
100
|
+
metadata: { updated: true },
|
|
101
|
+
},
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
assert.strictEqual(result.updated, true);
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
describe("BatchesService", () => {
|
|
109
|
+
it("should create a batch", async () => {
|
|
110
|
+
const api = createApi({ apiKey: "test-key" });
|
|
111
|
+
const result = await api.batches.createBatch({
|
|
112
|
+
payload: {
|
|
113
|
+
requests: [
|
|
114
|
+
{ model: "gpt-4", messages: [{ role: "user", content: "Hello" }] },
|
|
115
|
+
],
|
|
116
|
+
},
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
assert.strictEqual(result.id, "batch-123");
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it("should retrieve a batch", async () => {
|
|
123
|
+
const api = createApi({ apiKey: "test-key" });
|
|
124
|
+
const result = await api.batches.retrieveBatch({
|
|
125
|
+
payload: { batch_id: "batch-123" },
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
assert.strictEqual(result.status, "pending");
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it("should list batches", async () => {
|
|
132
|
+
const api = createApi({ apiKey: "test-key" });
|
|
133
|
+
const result = await api.batches.listBatches({
|
|
134
|
+
payload: {},
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
assert.strictEqual(result.length, 1);
|
|
138
|
+
assert.strictEqual(result[0].id, "batch-123");
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it("should cancel a batch", async () => {
|
|
142
|
+
const api = createApi({ apiKey: "test-key" });
|
|
143
|
+
const result = await api.batches.cancelBatch({
|
|
144
|
+
payload: { batch_id: "batch-123" },
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
assert.strictEqual(result.cancelled, true);
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
});
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
// This is a legacy utility test file.
|
|
4
|
+
// It checks the small helper surfaces like validation, streaming helpers, and file utilities in plain terms.
|
|
5
|
+
|
|
6
|
+
const assert = require("assert");
|
|
7
|
+
const { validation, streaming, fileHelpers } = require("../src/index").utils;
|
|
8
|
+
|
|
9
|
+
describe("Utility Functions", () => {
|
|
10
|
+
describe("Validation", () => {
|
|
11
|
+
it("should validate required parameters", () => {
|
|
12
|
+
const schema = {
|
|
13
|
+
required: ["name"],
|
|
14
|
+
properties: {
|
|
15
|
+
name: { type: "string" },
|
|
16
|
+
age: { type: "number" },
|
|
17
|
+
},
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
// Valid case
|
|
21
|
+
expect(() =>
|
|
22
|
+
validation.validateParams({ name: "Test" }, schema)
|
|
23
|
+
).not.toThrow();
|
|
24
|
+
|
|
25
|
+
// Invalid case - missing required field
|
|
26
|
+
expect(() => validation.validateParams({}, schema)).toThrow();
|
|
27
|
+
|
|
28
|
+
// Invalid case - wrong type
|
|
29
|
+
expect(() => validation.validateParams({ name: 123 }, schema)).toThrow();
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
describe("Streaming", () => {
|
|
34
|
+
it("should create a streaming handler", () => {
|
|
35
|
+
const onData = jest.fn();
|
|
36
|
+
const onError = jest.fn();
|
|
37
|
+
const onFinish = jest.fn();
|
|
38
|
+
|
|
39
|
+
const handler = streaming.createStreamHandler({
|
|
40
|
+
onData,
|
|
41
|
+
onError,
|
|
42
|
+
onFinish,
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
expect(handler).toHaveProperty("controller");
|
|
46
|
+
expect(handler).toHaveProperty("stream");
|
|
47
|
+
|
|
48
|
+
// Mock data event
|
|
49
|
+
const mockChunk = { choices: [{ delta: { content: "Hello" } }] };
|
|
50
|
+
handler.onChunk(mockChunk);
|
|
51
|
+
expect(onData).toHaveBeenCalledWith(mockChunk);
|
|
52
|
+
|
|
53
|
+
// Mock done event
|
|
54
|
+
handler.onDone();
|
|
55
|
+
expect(onFinish).toHaveBeenCalled();
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
describe("File Helpers", () => {
|
|
60
|
+
it("should determine file type from path", () => {
|
|
61
|
+
expect(fileHelpers.getFileTypeFromPath("image.png")).toBe("image");
|
|
62
|
+
expect(fileHelpers.getFileTypeFromPath("audio.mp3")).toBe("audio");
|
|
63
|
+
expect(fileHelpers.getFileTypeFromPath("document.pdf")).toBe("document");
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it("should validate file size", () => {
|
|
67
|
+
const mockFile = { size: 1024 * 1024 }; // 1MB
|
|
68
|
+
|
|
69
|
+
// File under limit
|
|
70
|
+
expect(fileHelpers.validateFileSize(mockFile, 2 * 1024 * 1024)).toBe(
|
|
71
|
+
true
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
// File exceeds limit
|
|
75
|
+
expect(fileHelpers.validateFileSize(mockFile, 512 * 1024)).toBe(false);
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
});
|