-
Notifications
You must be signed in to change notification settings - Fork 3
refactor: flatten client structure and add Zod validation to RPC client #168
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
f40725b
1834251
928562a
ae6009b
6b58654
bf9336b
0afc027
20b5c79
08fcbeb
bcfdcd9
2a1f57f
b3e3a9c
2b7bfcb
c3b30e0
034b905
32bf1ff
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -32,16 +32,12 @@ catalogs: | |
| prod: | ||
| '@modelcontextprotocol/sdk': ^1.19.1 | ||
| '@orama/orama': ^3.1.11 | ||
| '@stackone/stackone-client-ts': 4.32.2 | ||
| json-schema: ^0.4.0 | ||
|
Comment on lines
32
to
35
|
||
|
|
||
| enablePrePostScripts: true | ||
|
|
||
| minimumReleaseAge: 1440 | ||
|
|
||
| minimumReleaseAgeExclude: | ||
| - '@stackone/stackone-client-ts' | ||
|
|
||
| onlyBuiltDependencies: | ||
| - '@biomejs/biome' | ||
| - esbuild | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,104 @@ | ||||||
| import { RpcClient } from './rpc-client'; | ||||||
| import { StackOneAPIError } from './utils/errors'; | ||||||
|
|
||||||
| test('should successfully execute an RPC action', async () => { | ||||||
| const client = new RpcClient({ | ||||||
| security: { username: 'test-api-key' }, | ||||||
| }); | ||||||
|
|
||||||
| const response = await client.actions.rpcAction({ | ||||||
| action: 'hris_get_employee', | ||||||
| body: { fields: 'name,email' }, | ||||||
| path: { id: 'emp-123' }, | ||||||
| }); | ||||||
|
|
||||||
| // Response matches server's ActionsRpcResponseApiModel structure | ||||||
| expect(response).toHaveProperty('data'); | ||||||
| expect(response.data).toMatchObject({ | ||||||
| id: 'emp-123', | ||||||
| name: 'Test Employee', | ||||||
| }); | ||||||
| }); | ||||||
|
|
||||||
| test('should send correct payload structure', async () => { | ||||||
| const client = new RpcClient({ | ||||||
| security: { username: 'test-api-key' }, | ||||||
| }); | ||||||
|
|
||||||
| const response = await client.actions.rpcAction({ | ||||||
| action: 'custom_action', | ||||||
| body: { key: 'value' }, | ||||||
| headers: { 'x-custom': 'header' }, | ||||||
| path: { id: '123' }, | ||||||
| query: { filter: 'active' }, | ||||||
| }); | ||||||
|
|
||||||
| // Response matches server's ActionsRpcResponseApiModel structure | ||||||
| expect(response.data).toMatchObject({ | ||||||
| action: 'custom_action', | ||||||
| received: { | ||||||
| body: { key: 'value' }, | ||||||
| headers: { 'x-custom': 'header' }, | ||||||
| path: { id: '123' }, | ||||||
| query: { filter: 'active' }, | ||||||
| }, | ||||||
| }); | ||||||
| }); | ||||||
|
|
||||||
| test('should handle list actions with array data', async () => { | ||||||
| const client = new RpcClient({ | ||||||
| security: { username: 'test-api-key' }, | ||||||
| }); | ||||||
|
|
||||||
| const response = await client.actions.rpcAction({ | ||||||
| action: 'hris_list_employees', | ||||||
| }); | ||||||
|
|
||||||
| // Response data can be an array (matches RpcActionResponseData union type) | ||||||
| expect(Array.isArray(response.data)).toBe(true); | ||||||
| expect(response.data).toMatchObject([ | ||||||
| { id: expect.any(String), name: expect.any(String) }, | ||||||
| { id: expect.any(String), name: expect.any(String) }, | ||||||
| ]); | ||||||
| }); | ||||||
|
|
||||||
| test('should throw StackOneAPIError on server error', async () => { | ||||||
| const client = new RpcClient({ | ||||||
| security: { username: 'test-api-key' }, | ||||||
| }); | ||||||
|
|
||||||
| await expect( | ||||||
| client.actions.rpcAction({ | ||||||
| action: 'test_error_action', | ||||||
| }) | ||||||
| ).rejects.toThrow(StackOneAPIError); | ||||||
| }); | ||||||
|
|
||||||
| test('should include request body in error for debugging', async () => { | ||||||
| const client = new RpcClient({ | ||||||
| security: { username: 'test-api-key' }, | ||||||
| }); | ||||||
|
|
||||||
| await expect( | ||||||
| client.actions.rpcAction({ | ||||||
| action: 'test_error_action', | ||||||
| body: { debug: 'data' }, | ||||||
| }) | ||||||
| ).rejects.toMatchObject({ | ||||||
| statusCode: 500, | ||||||
| requestBody: { action: 'test_error_action' }, | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P2: The test assertion doesn't verify that the Prompt for AI agents
|
||||||
| requestBody: { action: 'test_error_action' }, | |
| requestBody: { action: 'test_error_action', body: { debug: 'data' } }, |
| Original file line number | Diff line number | Diff line change | ||
|---|---|---|---|---|
| @@ -0,0 +1,149 @@ | ||||
| import { z } from 'zod'; | ||||
| import { StackOneAPIError } from './utils/errors'; | ||||
|
|
||||
|
Comment on lines
+2
to
+3
|
||||
| import { StackOneAPIError } from './utils/errors'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
P1: Rule violated: Flag Security Vulnerabilities
The serverURL config parameter accepts any string without validating HTTPS. Since authentication credentials (Basic auth header with username/password) are sent to this URL, a misconfigured HTTP URL would transmit credentials in plaintext. Add HTTPS validation to the schema to prevent credential exposure.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/rpc-client.ts, line 37:
<comment>The `serverURL` config parameter accepts any string without validating HTTPS. Since authentication credentials (Basic auth header with username/password) are sent to this URL, a misconfigured HTTP URL would transmit credentials in plaintext. Add HTTPS validation to the schema to prevent credential exposure.</comment>
<file context>
@@ -0,0 +1,128 @@
+ * Zod schema for RPC client configuration validation
+ */
+const rpcClientConfigSchema = z.object({
+ serverURL: z.string().optional(),
+ security: z.object({
+ username: z.string(),
</file context>
Copilot
AI
Dec 9, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The error handling for response validation uses response.status which would be 200 (OK) when validation fails. This is misleading since the issue is with the response format, not an HTTP error. Consider using a different status code (e.g., 500) or creating a separate error type for validation failures.
Additionally, response.json() on line 99 can throw if the response body is not valid JSON, but this is not caught. Consider wrapping it in a try-catch to handle malformed responses gracefully.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The
rpc-client.tsfile imports and uses Zod for runtime validation, butzodis listed indevDependenciesinstead ofdependencies. This will cause runtime errors when the package is installed in production environments.Move
zodfromdevDependenciestodependenciesto ensure it's available at runtime.