@blokjs/trigger-grpc 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.
- package/.env.example +8 -0
- package/CHANGELOG.md +143 -0
- package/README.md +25 -0
- package/__tests__/unit/GRpcTrigger.test.ts +313 -0
- package/__tests__/unit/GrpcClient.test.ts +138 -0
- package/__tests__/unit/GrpcServer.test.ts +127 -0
- package/__tests__/unit/MessageDecode.test.ts +314 -0
- package/__tests__/unit/NanoSDK.test.ts +162 -0
- package/__tests__/unit/Nodes.test.ts +16 -0
- package/buf.gen.yaml +11 -0
- package/buf.yaml +9 -0
- package/dist/GRpcTrigger.d.ts +18 -0
- package/dist/GRpcTrigger.js +219 -0
- package/dist/GRpcTrigger.js.map +1 -0
- package/dist/GrpcClient.d.ts +28 -0
- package/dist/GrpcClient.js +72 -0
- package/dist/GrpcClient.js.map +1 -0
- package/dist/GrpcServer.d.ts +14 -0
- package/dist/GrpcServer.js +54 -0
- package/dist/GrpcServer.js.map +1 -0
- package/dist/MessageDecode.d.ts +12 -0
- package/dist/MessageDecode.js +133 -0
- package/dist/MessageDecode.js.map +1 -0
- package/dist/NanoSDK.d.ts +13 -0
- package/dist/NanoSDK.js +169 -0
- package/dist/NanoSDK.js.map +1 -0
- package/dist/Nodes.d.ts +5 -0
- package/dist/Nodes.js +13 -0
- package/dist/Nodes.js.map +1 -0
- package/dist/Workflows.d.ts +3 -0
- package/dist/Workflows.js +5 -0
- package/dist/Workflows.js.map +1 -0
- package/dist/gen/workflow_pb.d.ts +113 -0
- package/dist/gen/workflow_pb.js +76 -0
- package/dist/gen/workflow_pb.js.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +17 -0
- package/dist/index.js.map +1 -0
- package/dist/opentelemetry_metrics.d.ts +1 -0
- package/dist/opentelemetry_metrics.js +21 -0
- package/dist/opentelemetry_metrics.js.map +1 -0
- package/dist/proto/workflow.proto +33 -0
- package/dist/server.d.ts +1 -0
- package/dist/server.js +12 -0
- package/dist/server.js.map +1 -0
- package/dist/types/Message.d.ts +0 -0
- package/dist/types/Message.js +2 -0
- package/dist/types/Message.js.map +1 -0
- package/dist/types/RuntimeWorkflow.d.ts +5 -0
- package/dist/types/RuntimeWorkflow.js +3 -0
- package/dist/types/RuntimeWorkflow.js.map +1 -0
- package/dist/types/Workflows.d.ts +5 -0
- package/dist/types/Workflows.js +3 -0
- package/dist/types/Workflows.js.map +1 -0
- package/localhost+2-key.pem +28 -0
- package/localhost+2.pem +27 -0
- package/package.json +60 -0
- package/proto/workflow.proto +33 -0
- package/src/GRpcTrigger.ts +252 -0
- package/src/GrpcClient.ts +87 -0
- package/src/GrpcServer.ts +63 -0
- package/src/MessageDecode.ts +142 -0
- package/src/NanoSDK.ts +146 -0
- package/src/Nodes.ts +12 -0
- package/src/Workflows.ts +5 -0
- package/src/gen/workflow_pb.ts +142 -0
- package/src/index.ts +22 -0
- package/src/opentelemetry_metrics.ts +26 -0
- package/src/server.ts +8 -0
- package/src/types/Message.ts +0 -0
- package/src/types/RuntimeWorkflow.ts +7 -0
- package/src/types/Workflows.ts +7 -0
- package/tsconfig.json +34 -0
- package/vitest.config.ts +29 -0
- package/workflows/countries.json +31 -0
package/.env.example
ADDED
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# @blokjs/trigger-grpc
|
|
2
|
+
|
|
3
|
+
## 0.2.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- Initial public release of Blok packages.
|
|
8
|
+
|
|
9
|
+
This release includes:
|
|
10
|
+
|
|
11
|
+
- Core packages: @blokjs/shared, @blokjs/helper, @blokjs/runner
|
|
12
|
+
- Node packages: @blokjs/api-call, @blokjs/if-else, @blokjs/react
|
|
13
|
+
- Trigger packages: pubsub, queue, webhook, websocket, worker, cron, grpc
|
|
14
|
+
- CLI tool: blokctl
|
|
15
|
+
- Editor support: @blokjs/lsp-server, @blokjs/syntax
|
|
16
|
+
|
|
17
|
+
### Patch Changes
|
|
18
|
+
|
|
19
|
+
- Updated dependencies
|
|
20
|
+
- @blokjs/shared@0.2.0
|
|
21
|
+
- @blokjs/helper@0.2.0
|
|
22
|
+
- @blokjs/runner@0.2.0
|
|
23
|
+
- @blokjs/api-call@0.2.0
|
|
24
|
+
- @blokjs/if-else@0.2.0
|
|
25
|
+
|
|
26
|
+
## 0.0.14
|
|
27
|
+
|
|
28
|
+
### Patch Changes
|
|
29
|
+
|
|
30
|
+
- Updated dependencies
|
|
31
|
+
- @blokjs/runner@0.1.26
|
|
32
|
+
- @blokjs/if-else@0.0.30
|
|
33
|
+
- @blokjs/api-call@0.1.29
|
|
34
|
+
|
|
35
|
+
## 0.0.13
|
|
36
|
+
|
|
37
|
+
### Patch Changes
|
|
38
|
+
|
|
39
|
+
- Updated dependencies
|
|
40
|
+
- @blokjs/runner@0.1.25
|
|
41
|
+
- @blokjs/if-else@0.0.29
|
|
42
|
+
- @blokjs/api-call@0.1.28
|
|
43
|
+
|
|
44
|
+
## 0.0.12
|
|
45
|
+
|
|
46
|
+
### Patch Changes
|
|
47
|
+
|
|
48
|
+
- Updated dependencies
|
|
49
|
+
- @blokjs/runner@0.1.24
|
|
50
|
+
- @blokjs/if-else@0.0.28
|
|
51
|
+
- @blokjs/api-call@0.1.27
|
|
52
|
+
|
|
53
|
+
## 0.0.11
|
|
54
|
+
|
|
55
|
+
### Patch Changes
|
|
56
|
+
|
|
57
|
+
- Updated dependencies
|
|
58
|
+
- @blokjs/runner@0.1.23
|
|
59
|
+
- @blokjs/if-else@0.0.27
|
|
60
|
+
- @blokjs/api-call@0.1.26
|
|
61
|
+
|
|
62
|
+
## 0.0.10
|
|
63
|
+
|
|
64
|
+
### Patch Changes
|
|
65
|
+
|
|
66
|
+
- Updated dependencies
|
|
67
|
+
- @blokjs/runner@0.1.22
|
|
68
|
+
- @blokjs/if-else@0.0.26
|
|
69
|
+
- @blokjs/api-call@0.1.25
|
|
70
|
+
|
|
71
|
+
## 0.0.9
|
|
72
|
+
|
|
73
|
+
### Patch Changes
|
|
74
|
+
|
|
75
|
+
- Updated dependencies
|
|
76
|
+
- @blokjs/runner@0.1.21
|
|
77
|
+
- @blokjs/if-else@0.0.25
|
|
78
|
+
- @blokjs/api-call@0.1.24
|
|
79
|
+
|
|
80
|
+
## 0.0.8
|
|
81
|
+
|
|
82
|
+
### Patch Changes
|
|
83
|
+
|
|
84
|
+
- added GRPC remote node execution server and client (NodeJS)
|
|
85
|
+
- Updated dependencies
|
|
86
|
+
- @blokjs/helper@0.1.5
|
|
87
|
+
- @blokjs/runner@0.1.20
|
|
88
|
+
- @blokjs/if-else@0.0.24
|
|
89
|
+
- @blokjs/api-call@0.1.23
|
|
90
|
+
|
|
91
|
+
## 0.0.7
|
|
92
|
+
|
|
93
|
+
### Patch Changes
|
|
94
|
+
|
|
95
|
+
- Updated dependencies
|
|
96
|
+
- @blokjs/runner@0.1.19
|
|
97
|
+
- @blokjs/shared@0.0.9
|
|
98
|
+
- @blokjs/if-else@0.0.23
|
|
99
|
+
- @blokjs/api-call@0.1.22
|
|
100
|
+
|
|
101
|
+
## 0.0.6
|
|
102
|
+
|
|
103
|
+
### Patch Changes
|
|
104
|
+
|
|
105
|
+
- Added examples and create project' command to include examples and 'create node' command with options for type ('module' or 'class') and template ('class' or 'ui')
|
|
106
|
+
- Updated dependencies
|
|
107
|
+
- @blokjs/if-else@0.0.22
|
|
108
|
+
- @blokjs/api-call@0.1.21
|
|
109
|
+
- @blokjs/runner@0.1.18
|
|
110
|
+
- @blokjs/shared@0.0.8
|
|
111
|
+
|
|
112
|
+
## 0.0.5
|
|
113
|
+
|
|
114
|
+
### Patch Changes
|
|
115
|
+
|
|
116
|
+
- Successfully implemented all the enhancements and improvements identified during our internal hackathon.
|
|
117
|
+
|
|
118
|
+
## 0.0.4
|
|
119
|
+
|
|
120
|
+
### Patch Changes
|
|
121
|
+
|
|
122
|
+
- Updated dependencies
|
|
123
|
+
- @blokjs/runner@0.1.17
|
|
124
|
+
- @blokjs/if-else@0.0.21
|
|
125
|
+
- @blokjs/api-call@0.1.20
|
|
126
|
+
|
|
127
|
+
## 0.0.3
|
|
128
|
+
|
|
129
|
+
### Patch Changes
|
|
130
|
+
|
|
131
|
+
- Added support for YAML, XML and TOML in the workflow file. Upgraded package version recommended by Dependabot.
|
|
132
|
+
- Updated dependencies
|
|
133
|
+
- @blokjs/if-else@0.0.20
|
|
134
|
+
- @blokjs/api-call@0.1.19
|
|
135
|
+
- @blokjs/helper@0.1.4
|
|
136
|
+
- @blokjs/runner@0.1.16
|
|
137
|
+
- @blokjs/shared@0.0.7
|
|
138
|
+
|
|
139
|
+
## 0.0.2
|
|
140
|
+
|
|
141
|
+
### Patch Changes
|
|
142
|
+
|
|
143
|
+
- Implemented grpc client library
|
package/README.md
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Use the gRPC protocol instead of the Connect protocol
|
|
2
|
+
|
|
3
|
+
On Node.js, we support three protocols:
|
|
4
|
+
|
|
5
|
+
1. The gRPC protocol that is used throughout the gRPC ecosystem.
|
|
6
|
+
2. The gRPC-Web protocol used by grpc/grpc-web, allowing servers to interop with grpc-web front-ends without the need for an intermediary proxy (such as Envoy).
|
|
7
|
+
3. The new Connect protocol, a simple, HTTP-based protocol that works over HTTP/1.1 or HTTP/2. It takes the best portions of gRPC and gRPC-Web, including streaming, and packages them into a protocol that works equally well in browsers, monoliths, and microservices. The Connect protocol is what we think the gRPC protocol should be. By default, JSON- and binary-encoded Protobuf is supported.
|
|
8
|
+
|
|
9
|
+
So far, we have been using the ```http://``` scheme in our examples. We were not using TLS (Transport Layer Security). If you want to use gRPC and browser clients during local development, you need TLS.
|
|
10
|
+
|
|
11
|
+
Actually, that only takes a minute to set up! We will use ```mkcert``` to make a certificate. If you don't have it installed yet, please run the following commands:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
brew install mkcert
|
|
15
|
+
mkcert -install
|
|
16
|
+
mkcert localhost 127.0.0.1 ::1
|
|
17
|
+
export NODE_EXTRA_CA_CERTS="$(mkcert -CAROOT)/rootCA.pem"
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
If you don't use macOS or ```brew```, see the mkcert docs for instructions. You can copy the last line to your ```~/.zprofile``` or ```~/.profile```, so that the environment variable for Node.js is set every time you open a terminal.
|
|
21
|
+
|
|
22
|
+
If you already use ```mkcert```, just run ```mkcert localhost 127.0.0.1 ::1``` to issue a certificate for our example server.
|
|
23
|
+
|
|
24
|
+
Reference:
|
|
25
|
+
https://connectrpc.com/docs/node/getting-started/
|
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
|
|
3
|
+
// Mock OpenTelemetry
|
|
4
|
+
vi.mock("@opentelemetry/api", () => ({
|
|
5
|
+
trace: {
|
|
6
|
+
getTracer: () => ({
|
|
7
|
+
startActiveSpan: (_name: string, fn: (span: any) => any) =>
|
|
8
|
+
fn({
|
|
9
|
+
setAttribute: vi.fn(),
|
|
10
|
+
setStatus: vi.fn(),
|
|
11
|
+
recordException: vi.fn(),
|
|
12
|
+
end: vi.fn(),
|
|
13
|
+
}),
|
|
14
|
+
}),
|
|
15
|
+
},
|
|
16
|
+
metrics: {
|
|
17
|
+
getMeter: () => ({
|
|
18
|
+
createCounter: () => ({ add: vi.fn() }),
|
|
19
|
+
createGauge: () => ({ record: vi.fn() }),
|
|
20
|
+
}),
|
|
21
|
+
},
|
|
22
|
+
SpanStatusCode: { OK: 0, ERROR: 1 },
|
|
23
|
+
}));
|
|
24
|
+
|
|
25
|
+
// Mock fastify — must be a callable function (not arrow, since source may use `new`)
|
|
26
|
+
vi.mock("fastify", () => ({
|
|
27
|
+
default: () => ({
|
|
28
|
+
register: vi.fn(),
|
|
29
|
+
listen: vi.fn(),
|
|
30
|
+
use: vi.fn(),
|
|
31
|
+
}),
|
|
32
|
+
}));
|
|
33
|
+
|
|
34
|
+
// Mock uuid
|
|
35
|
+
vi.mock("uuid", () => ({ v4: () => "test-uuid-1234" }));
|
|
36
|
+
|
|
37
|
+
// Mock Nodes and Workflows
|
|
38
|
+
vi.mock("../../src/Nodes", () => ({ default: {} }));
|
|
39
|
+
vi.mock("../../src/Workflows", () => ({ default: {} }));
|
|
40
|
+
|
|
41
|
+
// Mock runner — TriggerBase and other classes must be constructable
|
|
42
|
+
vi.mock("@blokjs/runner", () => {
|
|
43
|
+
class MockNodeMap {
|
|
44
|
+
nodes: Record<string, any> = {};
|
|
45
|
+
addNode(key: string, val: any) {
|
|
46
|
+
this.nodes[key] = val;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
class MockTriggerBase {
|
|
51
|
+
configuration: any;
|
|
52
|
+
nodeMap: any;
|
|
53
|
+
constructor() {
|
|
54
|
+
this.configuration = {
|
|
55
|
+
name: "test-workflow",
|
|
56
|
+
version: "1.0.0",
|
|
57
|
+
init: vi.fn().mockResolvedValue(undefined),
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
createContext(_: any, name: string, id: string) {
|
|
61
|
+
return {
|
|
62
|
+
id,
|
|
63
|
+
request: { body: {}, headers: {}, query: {}, params: {} },
|
|
64
|
+
response: { data: { ok: true }, error: null, success: true, contentType: "application/json" },
|
|
65
|
+
error: { message: "" },
|
|
66
|
+
logger: { log: vi.fn(), error: vi.fn() },
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
async run(ctx: any) {
|
|
70
|
+
return {
|
|
71
|
+
ctx,
|
|
72
|
+
metrics: {
|
|
73
|
+
memory: { total: 10, min: 5, max: 15 },
|
|
74
|
+
cpu: { average: 50, total: 100, usage: 75, model: "test-cpu" },
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return {
|
|
81
|
+
DefaultLogger: class {
|
|
82
|
+
log() {}
|
|
83
|
+
error() {}
|
|
84
|
+
},
|
|
85
|
+
NodeMap: MockNodeMap,
|
|
86
|
+
TriggerBase: MockTriggerBase,
|
|
87
|
+
};
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
// Mock helper
|
|
91
|
+
vi.mock("@blokjs/helper", () => ({
|
|
92
|
+
Workflow: vi.fn().mockReturnValue({
|
|
93
|
+
addTrigger: vi.fn().mockReturnValue({
|
|
94
|
+
addStep: vi.fn().mockReturnValue({ name: "Remote Node", steps: [] }),
|
|
95
|
+
}),
|
|
96
|
+
}),
|
|
97
|
+
}));
|
|
98
|
+
|
|
99
|
+
// Mock shared — GlobalError must be a class
|
|
100
|
+
vi.mock("@blokjs/shared", () => ({
|
|
101
|
+
GlobalError: class GlobalError extends Error {
|
|
102
|
+
context: any;
|
|
103
|
+
constructor(message: string) {
|
|
104
|
+
super(message);
|
|
105
|
+
this.context = { message, code: undefined, json: null };
|
|
106
|
+
}
|
|
107
|
+
setCode(code: number) {
|
|
108
|
+
this.context.code = code;
|
|
109
|
+
}
|
|
110
|
+
hasJson() {
|
|
111
|
+
return this.context.json !== null && this.context.json !== undefined;
|
|
112
|
+
}
|
|
113
|
+
},
|
|
114
|
+
}));
|
|
115
|
+
|
|
116
|
+
import GRpcTrigger from "../../src/GRpcTrigger";
|
|
117
|
+
import { MessageEncoding, MessageType } from "../../src/gen/workflow_pb";
|
|
118
|
+
|
|
119
|
+
describe("GRpcTrigger", () => {
|
|
120
|
+
let trigger: GRpcTrigger;
|
|
121
|
+
|
|
122
|
+
beforeEach(() => {
|
|
123
|
+
vi.clearAllMocks();
|
|
124
|
+
trigger = new GRpcTrigger();
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
describe("constructor()", () => {
|
|
128
|
+
it("should create instance without errors", () => {
|
|
129
|
+
expect(trigger).toBeDefined();
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it("should call loadNodes and loadWorkflows", () => {
|
|
133
|
+
expect(trigger.getApp()).toBeDefined();
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
describe("getApp()", () => {
|
|
138
|
+
it("should return fastify instance", () => {
|
|
139
|
+
const app = trigger.getApp();
|
|
140
|
+
expect(app).toBeDefined();
|
|
141
|
+
expect(typeof app.register).toBe("function");
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
describe("listen()", () => {
|
|
146
|
+
it("should return 0", async () => {
|
|
147
|
+
const result = await trigger.listen();
|
|
148
|
+
expect(result).toBe(0);
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
describe("processRequest()", () => {
|
|
153
|
+
it("should register WorkflowService on the router", () => {
|
|
154
|
+
const mockService = vi.fn();
|
|
155
|
+
const mockRouter = { service: mockService };
|
|
156
|
+
trigger.processRequest(mockRouter as any, trigger);
|
|
157
|
+
expect(mockService).toHaveBeenCalled();
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
describe("executeWorkflow()", () => {
|
|
162
|
+
it("should execute workflow and return encoded response", async () => {
|
|
163
|
+
const payload = {
|
|
164
|
+
request: { body: {}, headers: {}, query: {}, params: {} },
|
|
165
|
+
response: { data: null, error: null, success: true },
|
|
166
|
+
};
|
|
167
|
+
const base64 = Buffer.from(JSON.stringify(payload)).toString("base64");
|
|
168
|
+
const request = {
|
|
169
|
+
Name: "test-workflow",
|
|
170
|
+
Message: base64,
|
|
171
|
+
Encoding: MessageEncoding[MessageEncoding.BASE64],
|
|
172
|
+
Type: MessageType[MessageType.JSON],
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
const result = await trigger.executeWorkflow(request as any);
|
|
176
|
+
expect(result).toBeDefined();
|
|
177
|
+
expect(result.Encoding).toBeDefined();
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it("should use requestId from query when available", async () => {
|
|
181
|
+
const payload = {
|
|
182
|
+
request: { body: {}, headers: {}, query: { requestId: "custom-req-id" }, params: {} },
|
|
183
|
+
response: { data: null, error: null, success: true },
|
|
184
|
+
};
|
|
185
|
+
const base64 = Buffer.from(JSON.stringify(payload)).toString("base64");
|
|
186
|
+
const request = {
|
|
187
|
+
Name: "test-workflow",
|
|
188
|
+
Message: base64,
|
|
189
|
+
Encoding: MessageEncoding[MessageEncoding.BASE64],
|
|
190
|
+
Type: MessageType[MessageType.JSON],
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
const result = await trigger.executeWorkflow(request as any);
|
|
194
|
+
expect(result).toBeDefined();
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
it("should handle remote node execution with workflow model", async () => {
|
|
198
|
+
const payload = {
|
|
199
|
+
request: { body: {}, headers: {}, query: {}, params: {} },
|
|
200
|
+
workflow: {
|
|
201
|
+
name: "Remote Node",
|
|
202
|
+
version: "1.0.0",
|
|
203
|
+
description: "Test",
|
|
204
|
+
trigger: { grpc: {} },
|
|
205
|
+
steps: [{ name: "node", node: "test-node", type: "module" }],
|
|
206
|
+
nodes: { node: { inputs: {} } },
|
|
207
|
+
},
|
|
208
|
+
};
|
|
209
|
+
const base64 = Buffer.from(JSON.stringify(payload)).toString("base64");
|
|
210
|
+
const request = {
|
|
211
|
+
Name: "test-node",
|
|
212
|
+
Message: base64,
|
|
213
|
+
Encoding: MessageEncoding[MessageEncoding.BASE64],
|
|
214
|
+
Type: MessageType[MessageType.JSON],
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
const result = await trigger.executeWorkflow(request as any);
|
|
218
|
+
expect(result).toBeDefined();
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
it("should handle runtime.python3 node type", async () => {
|
|
222
|
+
const payload = {
|
|
223
|
+
request: { body: {}, headers: {}, query: {}, params: {} },
|
|
224
|
+
workflow: {
|
|
225
|
+
name: "Remote Node",
|
|
226
|
+
version: "1.0.0",
|
|
227
|
+
description: "Test",
|
|
228
|
+
trigger: { grpc: {} },
|
|
229
|
+
steps: [{ name: "node", node: "py-node", type: "runtime.python3" }],
|
|
230
|
+
nodes: { node: { inputs: {} } },
|
|
231
|
+
},
|
|
232
|
+
};
|
|
233
|
+
const base64 = Buffer.from(JSON.stringify(payload)).toString("base64");
|
|
234
|
+
const request = {
|
|
235
|
+
Name: "py-node",
|
|
236
|
+
Message: base64,
|
|
237
|
+
Encoding: MessageEncoding[MessageEncoding.BASE64],
|
|
238
|
+
Type: MessageType[MessageType.JSON],
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
const result = await trigger.executeWorkflow(request as any);
|
|
242
|
+
expect(result).toBeDefined();
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
it("should handle local node type", async () => {
|
|
246
|
+
const payload = {
|
|
247
|
+
request: { body: {}, headers: {}, query: {}, params: {} },
|
|
248
|
+
workflow: {
|
|
249
|
+
name: "Remote Node",
|
|
250
|
+
version: "1.0.0",
|
|
251
|
+
description: "Test",
|
|
252
|
+
trigger: { grpc: {} },
|
|
253
|
+
steps: [{ name: "node", node: "local-node", type: "local" }],
|
|
254
|
+
nodes: { node: { inputs: {} } },
|
|
255
|
+
},
|
|
256
|
+
};
|
|
257
|
+
const base64 = Buffer.from(JSON.stringify(payload)).toString("base64");
|
|
258
|
+
const request = {
|
|
259
|
+
Name: "local-node",
|
|
260
|
+
Message: base64,
|
|
261
|
+
Encoding: MessageEncoding[MessageEncoding.BASE64],
|
|
262
|
+
Type: MessageType[MessageType.JSON],
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
const result = await trigger.executeWorkflow(request as any);
|
|
266
|
+
expect(result).toBeDefined();
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
it("should handle generic Error during execution", async () => {
|
|
270
|
+
const failTrigger = new GRpcTrigger();
|
|
271
|
+
(failTrigger as any).configuration.init = vi.fn().mockRejectedValue(new Error("Init failed"));
|
|
272
|
+
|
|
273
|
+
const payload = {
|
|
274
|
+
request: { body: {}, headers: {}, query: {}, params: {} },
|
|
275
|
+
};
|
|
276
|
+
const base64 = Buffer.from(JSON.stringify(payload)).toString("base64");
|
|
277
|
+
const request = {
|
|
278
|
+
Name: "fail-workflow",
|
|
279
|
+
Message: base64,
|
|
280
|
+
Encoding: MessageEncoding[MessageEncoding.BASE64],
|
|
281
|
+
Type: MessageType[MessageType.JSON],
|
|
282
|
+
};
|
|
283
|
+
|
|
284
|
+
const result = await failTrigger.executeWorkflow(request as any);
|
|
285
|
+
expect(result).toBeDefined();
|
|
286
|
+
expect(result.Encoding).toBe(MessageEncoding[MessageEncoding.BASE64]);
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
it("should set default contentType when empty", async () => {
|
|
290
|
+
const payload = {
|
|
291
|
+
request: { body: {}, headers: {}, query: {}, params: {} },
|
|
292
|
+
};
|
|
293
|
+
const base64 = Buffer.from(JSON.stringify(payload)).toString("base64");
|
|
294
|
+
const request = {
|
|
295
|
+
Name: "test-workflow",
|
|
296
|
+
Message: base64,
|
|
297
|
+
Encoding: MessageEncoding[MessageEncoding.BASE64],
|
|
298
|
+
Type: MessageType[MessageType.JSON],
|
|
299
|
+
};
|
|
300
|
+
|
|
301
|
+
// Override createContext to return empty contentType
|
|
302
|
+
const origCreateContext = (trigger as any).createContext.bind(trigger);
|
|
303
|
+
(trigger as any).createContext = (_: any, name: string, id: string) => {
|
|
304
|
+
const ctx = origCreateContext(_, name, id);
|
|
305
|
+
ctx.response.contentType = "";
|
|
306
|
+
return ctx;
|
|
307
|
+
};
|
|
308
|
+
|
|
309
|
+
const result = await trigger.executeWorkflow(request as any);
|
|
310
|
+
expect(result).toBeDefined();
|
|
311
|
+
});
|
|
312
|
+
});
|
|
313
|
+
});
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
|
|
3
|
+
// vi.mock factories are hoisted — cannot reference top-level variables.
|
|
4
|
+
// Use vi.hoisted() to define shared mocks.
|
|
5
|
+
const { mockExecuteWorkflow, mockCreateClient } = vi.hoisted(() => {
|
|
6
|
+
const mockExecuteWorkflow = vi.fn().mockResolvedValue({ Message: "response", Encoding: "BASE64", Type: "JSON" });
|
|
7
|
+
const mockCreateClient = vi.fn().mockReturnValue({ executeWorkflow: mockExecuteWorkflow });
|
|
8
|
+
return { mockExecuteWorkflow, mockCreateClient };
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
vi.mock("@connectrpc/connect", () => ({
|
|
12
|
+
createClient: mockCreateClient,
|
|
13
|
+
}));
|
|
14
|
+
|
|
15
|
+
vi.mock("@connectrpc/connect-node", () => ({
|
|
16
|
+
createGrpcTransport: vi.fn().mockReturnValue({ type: "grpc" }),
|
|
17
|
+
createGrpcWebTransport: vi.fn().mockReturnValue({ type: "grpc-web" }),
|
|
18
|
+
createConnectTransport: vi.fn().mockReturnValue({ type: "connect" }),
|
|
19
|
+
}));
|
|
20
|
+
|
|
21
|
+
import { createConnectTransport, createGrpcTransport, createGrpcWebTransport } from "@connectrpc/connect-node";
|
|
22
|
+
import GrpcClient, { TransportEnum, HttpVersionEnum, type RpcOptions } from "../../src/GrpcClient";
|
|
23
|
+
|
|
24
|
+
describe("GrpcClient", () => {
|
|
25
|
+
const defaultOpts: RpcOptions = {
|
|
26
|
+
host: "localhost",
|
|
27
|
+
port: 8433,
|
|
28
|
+
protocol: "http",
|
|
29
|
+
httpVersion: HttpVersionEnum.HTTP2,
|
|
30
|
+
transport: TransportEnum.GRPC,
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
beforeEach(() => {
|
|
34
|
+
vi.clearAllMocks();
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
describe("constructor()", () => {
|
|
38
|
+
it("should store options", () => {
|
|
39
|
+
const client = new GrpcClient(defaultOpts);
|
|
40
|
+
expect(client).toBeDefined();
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
describe("transport()", () => {
|
|
45
|
+
it("should create gRPC transport for GRPC type", () => {
|
|
46
|
+
const client = new GrpcClient({ ...defaultOpts, transport: TransportEnum.GRPC });
|
|
47
|
+
const transport = client.transport();
|
|
48
|
+
expect(createGrpcTransport).toHaveBeenCalledWith({
|
|
49
|
+
baseUrl: "http://localhost:8433/",
|
|
50
|
+
interceptors: [],
|
|
51
|
+
});
|
|
52
|
+
expect(transport).toEqual({ type: "grpc" });
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it("should create gRPC-Web transport for GRPC_WEB type", () => {
|
|
56
|
+
const client = new GrpcClient({ ...defaultOpts, transport: TransportEnum.GRPC_WEB });
|
|
57
|
+
const transport = client.transport();
|
|
58
|
+
expect(createGrpcWebTransport).toHaveBeenCalledWith({
|
|
59
|
+
baseUrl: "http://localhost:8433/",
|
|
60
|
+
httpVersion: HttpVersionEnum.HTTP2,
|
|
61
|
+
interceptors: [],
|
|
62
|
+
});
|
|
63
|
+
expect(transport).toEqual({ type: "grpc-web" });
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it("should create Connect transport for CONNECT type", () => {
|
|
67
|
+
const client = new GrpcClient({ ...defaultOpts, transport: TransportEnum.CONNECT });
|
|
68
|
+
const transport = client.transport();
|
|
69
|
+
expect(createConnectTransport).toHaveBeenCalledWith({
|
|
70
|
+
baseUrl: "http://localhost:8433/",
|
|
71
|
+
httpVersion: HttpVersionEnum.HTTP2,
|
|
72
|
+
interceptors: [],
|
|
73
|
+
});
|
|
74
|
+
expect(transport).toEqual({ type: "connect" });
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it("should throw for invalid transport type", () => {
|
|
78
|
+
const client = new GrpcClient({ ...defaultOpts, transport: "invalid" as TransportEnum });
|
|
79
|
+
expect(() => client.transport()).toThrow("Invalid transport type");
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it("should use correct baseUrl with custom protocol and port", () => {
|
|
83
|
+
const client = new GrpcClient({
|
|
84
|
+
...defaultOpts,
|
|
85
|
+
protocol: "https",
|
|
86
|
+
host: "api.example.com",
|
|
87
|
+
port: 9090,
|
|
88
|
+
transport: TransportEnum.GRPC,
|
|
89
|
+
});
|
|
90
|
+
client.transport();
|
|
91
|
+
expect(createGrpcTransport).toHaveBeenCalledWith({
|
|
92
|
+
baseUrl: "https://api.example.com:9090/",
|
|
93
|
+
interceptors: [],
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
describe("call()", () => {
|
|
99
|
+
it("should create client and execute workflow", async () => {
|
|
100
|
+
const client = new GrpcClient(defaultOpts);
|
|
101
|
+
const message = {
|
|
102
|
+
Name: "test",
|
|
103
|
+
Message: "data",
|
|
104
|
+
Encoding: "BASE64",
|
|
105
|
+
Type: "JSON",
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
const result = await client.call(message as any);
|
|
109
|
+
expect(mockCreateClient).toHaveBeenCalled();
|
|
110
|
+
expect(mockExecuteWorkflow).toHaveBeenCalledWith(message, undefined);
|
|
111
|
+
expect(result).toEqual({ Message: "response", Encoding: "BASE64", Type: "JSON" });
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it("should pass call options with headers", async () => {
|
|
115
|
+
const client = new GrpcClient(defaultOpts);
|
|
116
|
+
const message = { Name: "test", Message: "data", Encoding: "BASE64", Type: "JSON" };
|
|
117
|
+
const opts = { headers: { Authorization: "Bearer token123" } };
|
|
118
|
+
|
|
119
|
+
await client.call(message as any, opts);
|
|
120
|
+
expect(mockExecuteWorkflow).toHaveBeenCalledWith(message, opts);
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
describe("TransportEnum", () => {
|
|
125
|
+
it("should have correct values", () => {
|
|
126
|
+
expect(TransportEnum.GRPC).toBe("grpc");
|
|
127
|
+
expect(TransportEnum.GRPC_WEB).toBe("grpc-web");
|
|
128
|
+
expect(TransportEnum.CONNECT).toBe("connect");
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
describe("HttpVersionEnum", () => {
|
|
133
|
+
it("should have correct values", () => {
|
|
134
|
+
expect(HttpVersionEnum.HTTP1).toBe("1.1");
|
|
135
|
+
expect(HttpVersionEnum.HTTP2).toBe("2");
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
});
|