ts-procedures 2.0.1 → 2.1.1
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/build/implementations/http/express-rpc/index.d.ts +40 -39
- package/build/implementations/http/express-rpc/index.js +40 -20
- package/build/implementations/http/express-rpc/index.js.map +1 -1
- package/build/implementations/http/express-rpc/index.test.js +138 -58
- package/build/implementations/http/express-rpc/index.test.js.map +1 -1
- package/build/implementations/http/express-rpc/types.d.ts +1 -1
- package/build/implementations/types.d.ts +1 -1
- package/package.json +1 -1
- package/src/implementations/http/express-rpc/README.md +49 -25
- package/src/implementations/http/express-rpc/index.test.ts +170 -58
- package/src/implementations/http/express-rpc/index.ts +74 -45
- package/src/implementations/http/express-rpc/types.ts +6 -2
- package/src/implementations/types.ts +1 -1
- package/build/implementations/http/client/index.d.ts +0 -1
- package/build/implementations/http/client/index.js +0 -2
- package/build/implementations/http/client/index.js.map +0 -1
- package/build/implementations/http/express/example/factories.d.ts +0 -97
- package/build/implementations/http/express/example/factories.js +0 -4
- package/build/implementations/http/express/example/factories.js.map +0 -1
- package/build/implementations/http/express/example/procedures/auth.d.ts +0 -1
- package/build/implementations/http/express/example/procedures/auth.js +0 -22
- package/build/implementations/http/express/example/procedures/auth.js.map +0 -1
- package/build/implementations/http/express/example/procedures/users.d.ts +0 -1
- package/build/implementations/http/express/example/procedures/users.js +0 -30
- package/build/implementations/http/express/example/procedures/users.js.map +0 -1
- package/build/implementations/http/express/example/server.d.ts +0 -3
- package/build/implementations/http/express/example/server.js +0 -49
- package/build/implementations/http/express/example/server.js.map +0 -1
- package/build/implementations/http/express/example/server.test.d.ts +0 -1
- package/build/implementations/http/express/example/server.test.js +0 -110
- package/build/implementations/http/express/example/server.test.js.map +0 -1
- package/build/implementations/http/express/index.d.ts +0 -35
- package/build/implementations/http/express/index.js +0 -75
- package/build/implementations/http/express/index.js.map +0 -1
- package/build/implementations/http/express/index.test.d.ts +0 -1
- package/build/implementations/http/express/index.test.js +0 -329
- package/build/implementations/http/express/index.test.js.map +0 -1
- package/build/implementations/http/express/types.d.ts +0 -17
- package/build/implementations/http/express/types.js +0 -2
- package/build/implementations/http/express/types.js.map +0 -1
|
@@ -3,6 +3,20 @@ import { TProcedureRegistration } from '../../../index.js';
|
|
|
3
3
|
import { RPCConfig, RPCHttpRouteDoc } from '../../types.js';
|
|
4
4
|
import { ExtractContext, ProceduresFactory } from './types.js';
|
|
5
5
|
export type { RPCConfig, RPCHttpRouteDoc };
|
|
6
|
+
export type ExpressRPCAppBuilderConfig = {
|
|
7
|
+
/**
|
|
8
|
+
* An existing Express application instance to use.
|
|
9
|
+
* When provided, ensure to set up necessary middleware (e.g., json/body parser) beforehand.
|
|
10
|
+
* If not provided, a new instance will be created.
|
|
11
|
+
*/
|
|
12
|
+
app?: express.Express;
|
|
13
|
+
/** Optional path prefix for all RPC routes. */
|
|
14
|
+
pathPrefix?: string;
|
|
15
|
+
onRequestStart?: (req: express.Request) => void;
|
|
16
|
+
onRequestEnd?: (req: express.Request, res: express.Response) => void;
|
|
17
|
+
onSuccess?: (procedure: TProcedureRegistration, req: express.Request, res: express.Response) => void;
|
|
18
|
+
error?: (procedure: TProcedureRegistration, req: express.Request, res: express.Response, error: Error) => void;
|
|
19
|
+
};
|
|
6
20
|
/**
|
|
7
21
|
* Builder class for creating an Express application with RPC routes.
|
|
8
22
|
*
|
|
@@ -19,46 +33,43 @@ export type { RPCConfig, RPCHttpRouteDoc };
|
|
|
19
33
|
* const docs = rpcApp.docs; // RPC route documentation
|
|
20
34
|
*/
|
|
21
35
|
export declare class ExpressRPCAppBuilder {
|
|
22
|
-
readonly config?:
|
|
23
|
-
/**
|
|
24
|
-
* An existing Express application instance to use.
|
|
25
|
-
* When provided, ensure to set up necessary middleware (e.g., json/body parser) beforehand.
|
|
26
|
-
* If not provided, a new instance will be created.
|
|
27
|
-
*/
|
|
28
|
-
app?: express.Express;
|
|
29
|
-
onRequestStart?: (req: express.Request) => void;
|
|
30
|
-
onRequestEnd?: (req: express.Request, res: express.Response) => void;
|
|
31
|
-
onSuccess?: (procedure: TProcedureRegistration, req: express.Request, res: express.Response) => void;
|
|
32
|
-
error?: (procedure: TProcedureRegistration, req: express.Request, res: express.Response, error: Error) => void;
|
|
33
|
-
} | undefined;
|
|
36
|
+
readonly config?: ExpressRPCAppBuilderConfig | undefined;
|
|
34
37
|
/**
|
|
35
38
|
* Constructor for ExpressRPCAppBuilder.
|
|
36
39
|
*
|
|
37
40
|
* @param config
|
|
38
41
|
*/
|
|
39
|
-
constructor(config?:
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
42
|
+
constructor(config?: ExpressRPCAppBuilderConfig | undefined);
|
|
43
|
+
/**
|
|
44
|
+
* Generates the RPC route path based on the RPC configuration.
|
|
45
|
+
* The RPCConfig name can be a string or an array of strings to form nested paths.
|
|
46
|
+
*
|
|
47
|
+
* Example
|
|
48
|
+
* name: ['string', 'string-string', 'string']
|
|
49
|
+
* path: /string/string-string/string/version
|
|
50
|
+
* @param config
|
|
51
|
+
*/
|
|
52
|
+
static makeRPCHttpRoutePath({ config, prefix }: {
|
|
53
|
+
prefix?: string;
|
|
54
|
+
config: RPCConfig;
|
|
55
|
+
}): string;
|
|
56
|
+
/**
|
|
57
|
+
* Instance method wrapper for makeRPCHttpRoutePath that uses the builder's pathPrefix.
|
|
58
|
+
* @param config - The RPC configuration
|
|
59
|
+
*/
|
|
60
|
+
makeRPCHttpRoutePath(config: RPCConfig): string;
|
|
51
61
|
private factories;
|
|
52
62
|
private _app;
|
|
53
63
|
private _docs;
|
|
54
64
|
get app(): express.Express;
|
|
55
65
|
get docs(): RPCHttpRouteDoc[];
|
|
56
66
|
/**
|
|
57
|
-
* Registers a procedure factory with its context
|
|
58
|
-
* @param factory
|
|
59
|
-
* @param
|
|
67
|
+
* Registers a procedure factory with its context.
|
|
68
|
+
* @param factory - The procedure factory created by Procedures<Context, RPCConfig>()
|
|
69
|
+
* @param factoryContext - The context for procedure handlers. Can be a direct value,
|
|
70
|
+
* a sync function (req) => Context, or an async function (req) => Promise<Context>
|
|
60
71
|
*/
|
|
61
|
-
register<TFactory extends ProceduresFactory>(factory: TFactory,
|
|
72
|
+
register<TFactory extends ProceduresFactory>(factory: TFactory, factoryContext: ExtractContext<TFactory> | ((req: express.Request) => ExtractContext<TFactory> | Promise<ExtractContext<TFactory>>)): this;
|
|
62
73
|
/**
|
|
63
74
|
* Builds and returns the Express application with registered RPC routes.
|
|
64
75
|
* @return express.Application
|
|
@@ -68,15 +79,5 @@ export declare class ExpressRPCAppBuilder {
|
|
|
68
79
|
* Generates the RPC HTTP route for the given procedure.
|
|
69
80
|
* @param procedure
|
|
70
81
|
*/
|
|
71
|
-
buildRpcHttpRouteDoc
|
|
72
|
-
/**
|
|
73
|
-
* Generates the RPC route path based on the RPC configuration.
|
|
74
|
-
* The RPCConfig name can be a string or an array of strings to form nested paths.
|
|
75
|
-
*
|
|
76
|
-
* Example
|
|
77
|
-
* name: ['string', 'string-string', 'string']
|
|
78
|
-
* path: /rpc/string/string-string/string/version
|
|
79
|
-
* @param config
|
|
80
|
-
*/
|
|
81
|
-
makeRPCHttpRoutePath(config: RPCConfig): string;
|
|
82
|
+
private buildRpcHttpRouteDoc;
|
|
82
83
|
}
|
|
@@ -47,6 +47,31 @@ export class ExpressRPCAppBuilder {
|
|
|
47
47
|
});
|
|
48
48
|
}
|
|
49
49
|
}
|
|
50
|
+
/**
|
|
51
|
+
* Generates the RPC route path based on the RPC configuration.
|
|
52
|
+
* The RPCConfig name can be a string or an array of strings to form nested paths.
|
|
53
|
+
*
|
|
54
|
+
* Example
|
|
55
|
+
* name: ['string', 'string-string', 'string']
|
|
56
|
+
* path: /string/string-string/string/version
|
|
57
|
+
* @param config
|
|
58
|
+
*/
|
|
59
|
+
static makeRPCHttpRoutePath({ config, prefix }) {
|
|
60
|
+
const normalizedPrefix = prefix
|
|
61
|
+
? (prefix.startsWith('/') ? prefix : `/${prefix}`)
|
|
62
|
+
: '';
|
|
63
|
+
return `${normalizedPrefix}/${castArray(config.name).map(kebabCase).join('/')}/${String(config.version).trim()}`;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Instance method wrapper for makeRPCHttpRoutePath that uses the builder's pathPrefix.
|
|
67
|
+
* @param config - The RPC configuration
|
|
68
|
+
*/
|
|
69
|
+
makeRPCHttpRoutePath(config) {
|
|
70
|
+
return ExpressRPCAppBuilder.makeRPCHttpRoutePath({
|
|
71
|
+
config,
|
|
72
|
+
prefix: this.config?.pathPrefix,
|
|
73
|
+
});
|
|
74
|
+
}
|
|
50
75
|
factories = [];
|
|
51
76
|
_app = express();
|
|
52
77
|
_docs = [];
|
|
@@ -57,12 +82,13 @@ export class ExpressRPCAppBuilder {
|
|
|
57
82
|
return this._docs;
|
|
58
83
|
}
|
|
59
84
|
/**
|
|
60
|
-
* Registers a procedure factory with its context
|
|
61
|
-
* @param factory
|
|
62
|
-
* @param
|
|
85
|
+
* Registers a procedure factory with its context.
|
|
86
|
+
* @param factory - The procedure factory created by Procedures<Context, RPCConfig>()
|
|
87
|
+
* @param factoryContext - The context for procedure handlers. Can be a direct value,
|
|
88
|
+
* a sync function (req) => Context, or an async function (req) => Promise<Context>
|
|
63
89
|
*/
|
|
64
|
-
register(factory,
|
|
65
|
-
this.factories.push({ factory,
|
|
90
|
+
register(factory, factoryContext) {
|
|
91
|
+
this.factories.push({ factory, factoryContext });
|
|
66
92
|
return this;
|
|
67
93
|
}
|
|
68
94
|
/**
|
|
@@ -70,13 +96,16 @@ export class ExpressRPCAppBuilder {
|
|
|
70
96
|
* @return express.Application
|
|
71
97
|
*/
|
|
72
98
|
build() {
|
|
73
|
-
this.factories.forEach(({ factory,
|
|
99
|
+
this.factories.forEach(({ factory, factoryContext }) => {
|
|
74
100
|
factory.getProcedures().map((procedure) => {
|
|
75
101
|
const route = this.buildRpcHttpRouteDoc(procedure);
|
|
76
102
|
this._docs.push(route);
|
|
77
103
|
this._app[route.method](route.path, async (req, res) => {
|
|
78
104
|
try {
|
|
79
|
-
|
|
105
|
+
const context = typeof factoryContext === 'function'
|
|
106
|
+
? await factoryContext(req)
|
|
107
|
+
: factoryContext;
|
|
108
|
+
res.json(await procedure.handler(context, req.body));
|
|
80
109
|
if (this.config?.onSuccess) {
|
|
81
110
|
this.config.onSuccess(procedure, req, res);
|
|
82
111
|
}
|
|
@@ -109,7 +138,10 @@ export class ExpressRPCAppBuilder {
|
|
|
109
138
|
*/
|
|
110
139
|
buildRpcHttpRouteDoc(procedure) {
|
|
111
140
|
const { config } = procedure;
|
|
112
|
-
const path =
|
|
141
|
+
const path = ExpressRPCAppBuilder.makeRPCHttpRoutePath({
|
|
142
|
+
config,
|
|
143
|
+
prefix: this.config?.pathPrefix,
|
|
144
|
+
});
|
|
113
145
|
const method = 'post'; // RPCs use POST method
|
|
114
146
|
const jsonSchema = {};
|
|
115
147
|
if (config.schema?.params) {
|
|
@@ -124,17 +156,5 @@ export class ExpressRPCAppBuilder {
|
|
|
124
156
|
jsonSchema,
|
|
125
157
|
};
|
|
126
158
|
}
|
|
127
|
-
/**
|
|
128
|
-
* Generates the RPC route path based on the RPC configuration.
|
|
129
|
-
* The RPCConfig name can be a string or an array of strings to form nested paths.
|
|
130
|
-
*
|
|
131
|
-
* Example
|
|
132
|
-
* name: ['string', 'string-string', 'string']
|
|
133
|
-
* path: /rpc/string/string-string/string/version
|
|
134
|
-
* @param config
|
|
135
|
-
*/
|
|
136
|
-
makeRPCHttpRoutePath(config) {
|
|
137
|
-
return `/rpc/${castArray(config.name).map(kebabCase).join('/')}/${String(config.version).trim()}`;
|
|
138
|
-
}
|
|
139
159
|
}
|
|
140
160
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/implementations/http/express-rpc/index.ts"],"names":[],"mappings":"AAAA,OAAO,OAAO,MAAM,SAAS,CAAA;AAC7B,OAAO,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAA;AAG7C,OAAO,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAA;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/implementations/http/express-rpc/index.ts"],"names":[],"mappings":"AAAA,OAAO,OAAO,MAAM,SAAS,CAAA;AAC7B,OAAO,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAA;AAG7C,OAAO,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAA;AA6B7C;;;;;;;;;;;;;;GAcG;AACH,MAAM,OAAO,oBAAoB;IAMV;IALrB;;;;OAIG;IACH,YAAqB,MAAmC;QAAnC,WAAM,GAAN,MAAM,CAA6B;QACtD,IAAI,MAAM,EAAE,GAAG,EAAE,CAAC;YAChB,IAAI,CAAC,IAAI,GAAG,MAAM,CAAC,GAAG,CAAA;QACxB,CAAC;aAAM,CAAC;YACN,2CAA2C;YAC3C,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,CAAA;QAC/B,CAAC;QAED,IAAI,MAAM,EAAE,cAAc,EAAE,CAAC;YAC3B,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE;gBAC/B,MAAM,CAAC,cAAe,CAAC,GAAG,CAAC,CAAA;gBAC3B,IAAI,EAAE,CAAA;YACR,CAAC,CAAC,CAAA;QACJ,CAAC;QAED,IAAI,MAAM,EAAE,YAAY,EAAE,CAAC;YACzB,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE;gBAC/B,GAAG,CAAC,EAAE,CAAC,QAAQ,EAAE,GAAG,EAAE;oBACpB,MAAM,CAAC,YAAa,CAAC,GAAG,EAAE,GAAG,CAAC,CAAA;gBAChC,CAAC,CAAC,CAAA;gBACF,IAAI,EAAE,CAAA;YACR,CAAC,CAAC,CAAA;QACJ,CAAC;IACH,CAAC;IAED;;;;;;;;OAQG;IACH,MAAM,CAAC,oBAAoB,CAAC,EAAE,MAAM,EAAE,MAAM,EAA0C;QACpF,MAAM,gBAAgB,GAAG,MAAM;YAC7B,CAAC,CAAC,CAAC,MAAM,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,MAAM,EAAE,CAAC;YAClD,CAAC,CAAC,EAAE,CAAA;QAEN,OAAO,GAAG,gBAAgB,IAAI,SAAS,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE,CAAA;IAClH,CAAC;IAED;;;OAGG;IACH,oBAAoB,CAAC,MAAiB;QACpC,OAAO,oBAAoB,CAAC,oBAAoB,CAAC;YAC/C,MAAM;YACN,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,UAAU;SAChC,CAAC,CAAA;IACJ,CAAC;IAEO,SAAS,GAA8B,EAAE,CAAA;IAEzC,IAAI,GAAoB,OAAO,EAAE,CAAA;IACjC,KAAK,GAAsB,EAAE,CAAA;IAErC,IAAI,GAAG;QACL,OAAO,IAAI,CAAC,IAAI,CAAA;IAClB,CAAC;IAED,IAAI,IAAI;QACN,OAAO,IAAI,CAAC,KAAK,CAAA;IACnB,CAAC;IAED;;;;;OAKG;IACH,QAAQ,CACN,OAAiB,EACjB,cAE4F;QAE5F,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,cAAc,EAA6B,CAAC,CAAA;QAC3E,OAAO,IAAI,CAAA;IACb,CAAC;IAED;;;OAGG;IACH,KAAK;QACH,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,EAAE,OAAO,EAAE,cAAc,EAAE,EAAE,EAAE;YACrD,OAAO,CAAC,aAAa,EAAE,CAAC,GAAG,CAAC,CAAC,SAAiD,EAAE,EAAE;gBAChF,MAAM,KAAK,GAAG,IAAI,CAAC,oBAAoB,CAAC,SAAS,CAAC,CAAA;gBAElD,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;gBAEtB,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,EAAE;oBACrD,IAAI,CAAC;wBACH,MAAM,OAAO,GACX,OAAO,cAAc,KAAK,UAAU;4BAClC,CAAC,CAAC,MAAM,cAAc,CAAC,GAAG,CAAC;4BAC3B,CAAC,CAAE,cAAiD,CAAA;wBAExD,GAAG,CAAC,IAAI,CAAC,MAAM,SAAS,CAAC,OAAO,CAAC,OAAO,EAAE,GAAG,CAAC,IAAI,CAAC,CAAC,CAAA;wBACpD,IAAI,IAAI,CAAC,MAAM,EAAE,SAAS,EAAE,CAAC;4BAC3B,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,SAAS,EAAE,GAAG,EAAE,GAAG,CAAC,CAAA;wBAC5C,CAAC;wBACD,gCAAgC;wBAChC,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,CAAC;4BAChB,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;wBACjB,CAAC;oBACH,CAAC;oBAAC,OAAO,KAAK,EAAE,CAAC;wBACf,IAAI,IAAI,CAAC,MAAM,EAAE,KAAK,EAAE,CAAC;4BACvB,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,SAAS,EAAE,GAAG,EAAE,GAAG,EAAE,KAAc,CAAC,CAAA;4BACtD,OAAM;wBACR,CAAC;wBACD,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,CAAC;4BAChB,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;wBACjB,CAAC;wBACD,gDAAgD;wBAChD,IAAI,CAAC,GAAG,CAAC,WAAW,EAAE,CAAC;4BACrB,GAAG,CAAC,IAAI,CAAC,EAAE,KAAK,EAAG,KAAe,CAAC,OAAO,EAAE,CAAC,CAAA;wBAC/C,CAAC;oBACH,CAAC;gBACH,CAAC,CAAC,CAAA;YACJ,CAAC,CAAC,CAAA;QACJ,CAAC,CAAC,CAAA;QAEF,OAAO,IAAI,CAAC,IAAI,CAAA;IAClB,CAAC;IAED;;;OAGG;IACK,oBAAoB,CAAC,SAAiD;QAC5E,MAAM,EAAE,MAAM,EAAE,GAAG,SAAS,CAAA;QAC5B,MAAM,IAAI,GAAG,oBAAoB,CAAC,oBAAoB,CAAC;YACrD,MAAM;YACN,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,UAAU;SAChC,CAAC,CAAA;QACF,MAAM,MAAM,GAAG,MAAM,CAAA,CAAC,uBAAuB;QAC7C,MAAM,UAAU,GAAyC,EAAE,CAAA;QAE3D,IAAI,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC;YAC1B,UAAU,CAAC,IAAI,GAAG,MAAM,CAAC,MAAM,CAAC,MAAM,CAAA;QACxC,CAAC;QACD,IAAI,MAAM,CAAC,MAAM,EAAE,UAAU,EAAE,CAAC;YAC9B,UAAU,CAAC,QAAQ,GAAG,MAAM,CAAC,MAAM,CAAC,UAAU,CAAA;QAChD,CAAC;QAED,OAAO;YACL,IAAI;YACJ,MAAM;YACN,UAAU;SACX,CAAA;IACH,CAAC;CACF"}
|
|
@@ -8,7 +8,7 @@ import { ExpressRPCAppBuilder } from './index.js';
|
|
|
8
8
|
* ExpressRPCAppBuilder Test Suite
|
|
9
9
|
*
|
|
10
10
|
* Tests the RPC-style Express integration for ts-procedures.
|
|
11
|
-
* This builder creates POST routes at `/
|
|
11
|
+
* This builder creates POST routes at `/{name}/{version}` paths (with optional pathPrefix).
|
|
12
12
|
*/
|
|
13
13
|
describe('ExpressRPCAppBuilder', () => {
|
|
14
14
|
// --------------------------------------------------------------------------
|
|
@@ -22,7 +22,7 @@ describe('ExpressRPCAppBuilder', () => {
|
|
|
22
22
|
builder.register(RPC, () => ({ userId: '123' }));
|
|
23
23
|
const app = builder.build();
|
|
24
24
|
// JSON body should be parsed automatically
|
|
25
|
-
const res = await request(app).post('/
|
|
25
|
+
const res = await request(app).post('/echo/1').send({ message: 'hello' });
|
|
26
26
|
expect(res.status).toBe(200);
|
|
27
27
|
expect(res.body).toEqual({ message: 'hello' });
|
|
28
28
|
});
|
|
@@ -36,7 +36,7 @@ describe('ExpressRPCAppBuilder', () => {
|
|
|
36
36
|
const app = builder.build();
|
|
37
37
|
// Without json middleware, body won't be parsed (req.body is undefined)
|
|
38
38
|
const res = await request(app)
|
|
39
|
-
.post('/
|
|
39
|
+
.post('/echo/1')
|
|
40
40
|
.set('Content-Type', 'application/json')
|
|
41
41
|
.send(JSON.stringify({ message: 'hello' }));
|
|
42
42
|
// Request body is undefined since json middleware wasn't added
|
|
@@ -55,6 +55,57 @@ describe('ExpressRPCAppBuilder', () => {
|
|
|
55
55
|
});
|
|
56
56
|
});
|
|
57
57
|
// --------------------------------------------------------------------------
|
|
58
|
+
// pathPrefix Option Tests
|
|
59
|
+
// --------------------------------------------------------------------------
|
|
60
|
+
describe('pathPrefix option', () => {
|
|
61
|
+
test('uses custom pathPrefix for all routes', async () => {
|
|
62
|
+
const builder = new ExpressRPCAppBuilder({ pathPrefix: '/api/v1' });
|
|
63
|
+
const RPC = Procedures();
|
|
64
|
+
RPC.Create('Test', { name: 'test', version: 1 }, async () => ({ ok: true }));
|
|
65
|
+
builder.register(RPC, () => ({}));
|
|
66
|
+
const app = builder.build();
|
|
67
|
+
const res = await request(app).post('/api/v1/test/1').send({});
|
|
68
|
+
expect(res.status).toBe(200);
|
|
69
|
+
expect(res.body).toEqual({ ok: true });
|
|
70
|
+
});
|
|
71
|
+
test('pathPrefix without leading slash gets normalized', async () => {
|
|
72
|
+
const builder = new ExpressRPCAppBuilder({ pathPrefix: 'custom' });
|
|
73
|
+
const RPC = Procedures();
|
|
74
|
+
RPC.Create('Test', { name: 'test', version: 1 }, async () => ({ ok: true }));
|
|
75
|
+
builder.register(RPC, () => ({}));
|
|
76
|
+
const app = builder.build();
|
|
77
|
+
const res = await request(app).post('/custom/test/1').send({});
|
|
78
|
+
expect(res.status).toBe(200);
|
|
79
|
+
});
|
|
80
|
+
test('no prefix when pathPrefix not specified', async () => {
|
|
81
|
+
const builder = new ExpressRPCAppBuilder();
|
|
82
|
+
const RPC = Procedures();
|
|
83
|
+
RPC.Create('Test', { name: 'test', version: 1 }, async () => ({ ok: true }));
|
|
84
|
+
builder.register(RPC, () => ({}));
|
|
85
|
+
const app = builder.build();
|
|
86
|
+
const res = await request(app).post('/test/1').send({});
|
|
87
|
+
expect(res.status).toBe(200);
|
|
88
|
+
});
|
|
89
|
+
test('pathPrefix appears in generated docs', () => {
|
|
90
|
+
const builder = new ExpressRPCAppBuilder({ pathPrefix: '/api' });
|
|
91
|
+
const RPC = Procedures();
|
|
92
|
+
RPC.Create('Test', { name: 'test', version: 1 }, async () => ({}));
|
|
93
|
+
builder.register(RPC, () => ({}));
|
|
94
|
+
builder.build();
|
|
95
|
+
expect(builder.docs[0].path).toBe('/api/test/1');
|
|
96
|
+
});
|
|
97
|
+
test('pathPrefix /rpc restores original behavior', async () => {
|
|
98
|
+
const builder = new ExpressRPCAppBuilder({ pathPrefix: '/rpc' });
|
|
99
|
+
const RPC = Procedures();
|
|
100
|
+
RPC.Create('Users', { name: 'users', version: 1 }, async () => ({ users: [] }));
|
|
101
|
+
builder.register(RPC, () => ({}));
|
|
102
|
+
const app = builder.build();
|
|
103
|
+
const res = await request(app).post('/rpc/users/1').send({});
|
|
104
|
+
expect(res.status).toBe(200);
|
|
105
|
+
expect(builder.docs[0].path).toBe('/rpc/users/1');
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
// --------------------------------------------------------------------------
|
|
58
109
|
// Lifecycle Hooks Tests
|
|
59
110
|
// --------------------------------------------------------------------------
|
|
60
111
|
describe('lifecycle hooks', () => {
|
|
@@ -65,10 +116,10 @@ describe('ExpressRPCAppBuilder', () => {
|
|
|
65
116
|
RPC.Create('Test', { name: 'test', version: 1 }, async () => ({ ok: true }));
|
|
66
117
|
builder.register(RPC, () => ({}));
|
|
67
118
|
const app = builder.build();
|
|
68
|
-
await request(app).post('/
|
|
119
|
+
await request(app).post('/test/1').send({});
|
|
69
120
|
expect(onRequestStart).toHaveBeenCalledTimes(1);
|
|
70
121
|
expect(onRequestStart.mock.calls[0][0]).toHaveProperty('method', 'POST');
|
|
71
|
-
expect(onRequestStart.mock.calls[0][0]).toHaveProperty('path', '/
|
|
122
|
+
expect(onRequestStart.mock.calls[0][0]).toHaveProperty('path', '/test/1');
|
|
72
123
|
});
|
|
73
124
|
test('onRequestEnd is called after response finishes', async () => {
|
|
74
125
|
const onRequestEnd = vi.fn();
|
|
@@ -77,7 +128,7 @@ describe('ExpressRPCAppBuilder', () => {
|
|
|
77
128
|
RPC.Create('Test', { name: 'test', version: 1 }, async () => ({ ok: true }));
|
|
78
129
|
builder.register(RPC, () => ({}));
|
|
79
130
|
const app = builder.build();
|
|
80
|
-
await request(app).post('/
|
|
131
|
+
await request(app).post('/test/1').send({});
|
|
81
132
|
expect(onRequestEnd).toHaveBeenCalledTimes(1);
|
|
82
133
|
expect(onRequestEnd.mock.calls[0][0]).toHaveProperty('method', 'POST');
|
|
83
134
|
expect(onRequestEnd.mock.calls[0][1]).toHaveProperty('statusCode', 200);
|
|
@@ -89,7 +140,7 @@ describe('ExpressRPCAppBuilder', () => {
|
|
|
89
140
|
RPC.Create('Test', { name: 'test', version: 1 }, async () => ({ ok: true }));
|
|
90
141
|
builder.register(RPC, () => ({}));
|
|
91
142
|
const app = builder.build();
|
|
92
|
-
await request(app).post('/
|
|
143
|
+
await request(app).post('/test/1').send({});
|
|
93
144
|
expect(onSuccess).toHaveBeenCalledTimes(1);
|
|
94
145
|
expect(onSuccess.mock.calls[0][0]).toHaveProperty('name', 'Test');
|
|
95
146
|
});
|
|
@@ -102,7 +153,7 @@ describe('ExpressRPCAppBuilder', () => {
|
|
|
102
153
|
});
|
|
103
154
|
builder.register(RPC, () => ({}));
|
|
104
155
|
const app = builder.build();
|
|
105
|
-
await request(app).post('/
|
|
156
|
+
await request(app).post('/test/1').send({});
|
|
106
157
|
expect(onSuccess).not.toHaveBeenCalled();
|
|
107
158
|
});
|
|
108
159
|
test('hooks execute in correct order: start → handler → success → end', async () => {
|
|
@@ -119,7 +170,7 @@ describe('ExpressRPCAppBuilder', () => {
|
|
|
119
170
|
});
|
|
120
171
|
builder.register(RPC, () => ({}));
|
|
121
172
|
const app = builder.build();
|
|
122
|
-
await request(app).post('/
|
|
173
|
+
await request(app).post('/test/1').send({});
|
|
123
174
|
expect(order).toEqual(['start', 'handler', 'success', 'end']);
|
|
124
175
|
});
|
|
125
176
|
});
|
|
@@ -138,7 +189,7 @@ describe('ExpressRPCAppBuilder', () => {
|
|
|
138
189
|
});
|
|
139
190
|
builder.register(RPC, () => ({}));
|
|
140
191
|
const app = builder.build();
|
|
141
|
-
const res = await request(app).post('/
|
|
192
|
+
const res = await request(app).post('/test/1').send({});
|
|
142
193
|
expect(errorHandler).toHaveBeenCalledTimes(1);
|
|
143
194
|
expect(errorHandler.mock.calls[0][0]).toHaveProperty('name', 'Test');
|
|
144
195
|
expect(errorHandler.mock.calls[0][3]).toBeInstanceOf(Error);
|
|
@@ -154,7 +205,7 @@ describe('ExpressRPCAppBuilder', () => {
|
|
|
154
205
|
});
|
|
155
206
|
builder.register(RPC, () => ({}));
|
|
156
207
|
const app = builder.build();
|
|
157
|
-
const res = await request(app).post('/
|
|
208
|
+
const res = await request(app).post('/test/1').send({});
|
|
158
209
|
// Default error handler returns error message in JSON body
|
|
159
210
|
expect(res.body).toHaveProperty('error');
|
|
160
211
|
expect(res.body.error).toContain('Something went wrong');
|
|
@@ -169,7 +220,7 @@ describe('ExpressRPCAppBuilder', () => {
|
|
|
169
220
|
});
|
|
170
221
|
builder.register(RPC, () => ({}));
|
|
171
222
|
const app = builder.build();
|
|
172
|
-
const res = await request(app).post('/
|
|
223
|
+
const res = await request(app).post('/test/1').send({});
|
|
173
224
|
// Unhandled exceptions are caught and returned as error response
|
|
174
225
|
expect(res.body).toHaveProperty('error');
|
|
175
226
|
});
|
|
@@ -202,13 +253,39 @@ describe('ExpressRPCAppBuilder', () => {
|
|
|
202
253
|
.register(PublicRPC, () => ({ public: true }))
|
|
203
254
|
.register(PrivateRPC, () => ({ private: true }));
|
|
204
255
|
const app = builder.build();
|
|
205
|
-
const publicRes = await request(app).post('/
|
|
206
|
-
const privateRes = await request(app).post('/
|
|
256
|
+
const publicRes = await request(app).post('/public/1').send({});
|
|
257
|
+
const privateRes = await request(app).post('/private/1').send({});
|
|
207
258
|
expect(publicRes.body).toEqual({ isPublic: true });
|
|
208
259
|
expect(privateRes.body).toEqual({ isPrivate: true });
|
|
209
260
|
});
|
|
210
|
-
test('context
|
|
211
|
-
const
|
|
261
|
+
test('context can be a static object', async () => {
|
|
262
|
+
const factoryContext = { requestId: 'req-123' };
|
|
263
|
+
const builder = new ExpressRPCAppBuilder();
|
|
264
|
+
const RPC = Procedures();
|
|
265
|
+
RPC.Create('GetRequestId', { name: 'get-request-id', version: 1 }, async (ctx) => ({
|
|
266
|
+
id: ctx.requestId,
|
|
267
|
+
}));
|
|
268
|
+
builder.register(RPC, factoryContext);
|
|
269
|
+
const app = builder.build();
|
|
270
|
+
const res = await request(app).post('/get-request-id/1').send({});
|
|
271
|
+
expect(res.body).toEqual({ id: 'req-123' });
|
|
272
|
+
});
|
|
273
|
+
test('factoryContext can be async function', async () => {
|
|
274
|
+
const factoryContext = vi.fn(async () => {
|
|
275
|
+
return { requestId: 'req-456' };
|
|
276
|
+
});
|
|
277
|
+
const builder = new ExpressRPCAppBuilder();
|
|
278
|
+
const RPC = Procedures();
|
|
279
|
+
RPC.Create('GetRequestId', { name: 'get-request-id', version: 1 }, async (ctx) => ({
|
|
280
|
+
id: ctx.requestId,
|
|
281
|
+
}));
|
|
282
|
+
builder.register(RPC, factoryContext);
|
|
283
|
+
const app = builder.build();
|
|
284
|
+
await request(app).post('/get-request-id/1').send({});
|
|
285
|
+
expect(factoryContext).toHaveBeenCalledTimes(1);
|
|
286
|
+
});
|
|
287
|
+
test('factoryContext function receives Express request object', async () => {
|
|
288
|
+
const factoryContext = vi.fn((req) => ({
|
|
212
289
|
authHeader: req.headers.authorization,
|
|
213
290
|
}));
|
|
214
291
|
const builder = new ExpressRPCAppBuilder();
|
|
@@ -216,12 +293,12 @@ describe('ExpressRPCAppBuilder', () => {
|
|
|
216
293
|
RPC.Create('GetAuth', { name: 'get-auth', version: 1 }, async (ctx) => ({
|
|
217
294
|
auth: ctx.authHeader,
|
|
218
295
|
}));
|
|
219
|
-
builder.register(RPC,
|
|
296
|
+
builder.register(RPC, factoryContext);
|
|
220
297
|
const app = builder.build();
|
|
221
|
-
await request(app).post('/
|
|
222
|
-
expect(
|
|
223
|
-
expect(
|
|
224
|
-
expect(
|
|
298
|
+
await request(app).post('/get-auth/1').set('Authorization', 'Bearer token123').send({});
|
|
299
|
+
expect(factoryContext).toHaveBeenCalledTimes(1);
|
|
300
|
+
expect(factoryContext.mock.calls[0][0]).toHaveProperty('headers');
|
|
301
|
+
expect(factoryContext.mock.calls[0][0].headers).toHaveProperty('authorization', 'Bearer token123');
|
|
225
302
|
});
|
|
226
303
|
});
|
|
227
304
|
// --------------------------------------------------------------------------
|
|
@@ -235,8 +312,8 @@ describe('ExpressRPCAppBuilder', () => {
|
|
|
235
312
|
RPC.Create('MethodTwo', { name: 'method-two', version: 2 }, async () => ({ m: 2 }));
|
|
236
313
|
builder.register(RPC, () => ({}));
|
|
237
314
|
const app = builder.build();
|
|
238
|
-
const res1 = await request(app).post('/
|
|
239
|
-
const res2 = await request(app).post('/
|
|
315
|
+
const res1 = await request(app).post('/method-one/1').send({});
|
|
316
|
+
const res2 = await request(app).post('/method-two/2').send({});
|
|
240
317
|
expect(res1.status).toBe(200);
|
|
241
318
|
expect(res2.status).toBe(200);
|
|
242
319
|
expect(res1.body).toEqual({ m: 1 });
|
|
@@ -257,8 +334,8 @@ describe('ExpressRPCAppBuilder', () => {
|
|
|
257
334
|
builder.register(RPC, () => ({}));
|
|
258
335
|
builder.build();
|
|
259
336
|
expect(builder.docs).toHaveLength(2);
|
|
260
|
-
expect(builder.docs[0].path).toBe('/
|
|
261
|
-
expect(builder.docs[1].path).toBe('/
|
|
337
|
+
expect(builder.docs[0].path).toBe('/method-one/1');
|
|
338
|
+
expect(builder.docs[1].path).toBe('/nested/method/2');
|
|
262
339
|
});
|
|
263
340
|
test('passes request body to handler as params', async () => {
|
|
264
341
|
const builder = new ExpressRPCAppBuilder();
|
|
@@ -266,7 +343,7 @@ describe('ExpressRPCAppBuilder', () => {
|
|
|
266
343
|
RPC.Create('Echo', { name: 'echo', version: 1, schema: { params: v.object({ data: v.string() }) } }, async (ctx, params) => ({ received: params.data }));
|
|
267
344
|
builder.register(RPC, () => ({}));
|
|
268
345
|
const app = builder.build();
|
|
269
|
-
const res = await request(app).post('/
|
|
346
|
+
const res = await request(app).post('/echo/1').send({ data: 'test-data' });
|
|
270
347
|
expect(res.body).toEqual({ received: 'test-data' });
|
|
271
348
|
});
|
|
272
349
|
test('GET requests return 404 (RPC uses POST only)', async () => {
|
|
@@ -275,7 +352,7 @@ describe('ExpressRPCAppBuilder', () => {
|
|
|
275
352
|
RPC.Create('Test', { name: 'test', version: 1 }, async () => ({ ok: true }));
|
|
276
353
|
builder.register(RPC, () => ({}));
|
|
277
354
|
const app = builder.build();
|
|
278
|
-
const res = await request(app).get('/
|
|
355
|
+
const res = await request(app).get('/test/1');
|
|
279
356
|
expect(res.status).toBe(404);
|
|
280
357
|
});
|
|
281
358
|
});
|
|
@@ -287,36 +364,36 @@ describe('ExpressRPCAppBuilder', () => {
|
|
|
287
364
|
beforeEach(() => {
|
|
288
365
|
builder = new ExpressRPCAppBuilder();
|
|
289
366
|
});
|
|
290
|
-
test("simple string: 'users' → /
|
|
367
|
+
test("simple string: 'users' → /users/1", () => {
|
|
291
368
|
const path = builder.makeRPCHttpRoutePath({ name: 'users', version: 1 });
|
|
292
|
-
expect(path).toBe('/
|
|
369
|
+
expect(path).toBe('/users/1');
|
|
293
370
|
});
|
|
294
|
-
test("array name: ['users', 'get-by-id'] → /
|
|
371
|
+
test("array name: ['users', 'get-by-id'] → /users/get-by-id/1", () => {
|
|
295
372
|
const path = builder.makeRPCHttpRoutePath({ name: ['users', 'get-by-id'], version: 1 });
|
|
296
|
-
expect(path).toBe('/
|
|
373
|
+
expect(path).toBe('/users/get-by-id/1');
|
|
297
374
|
});
|
|
298
|
-
test("camelCase: 'getUserById' → /
|
|
375
|
+
test("camelCase: 'getUserById' → /get-user-by-id/1", () => {
|
|
299
376
|
const path = builder.makeRPCHttpRoutePath({ name: 'getUserById', version: 1 });
|
|
300
|
-
expect(path).toBe('/
|
|
377
|
+
expect(path).toBe('/get-user-by-id/1');
|
|
301
378
|
});
|
|
302
|
-
test("PascalCase: 'GetUserById' → /
|
|
379
|
+
test("PascalCase: 'GetUserById' → /get-user-by-id/1", () => {
|
|
303
380
|
const path = builder.makeRPCHttpRoutePath({ name: 'GetUserById', version: 1 });
|
|
304
|
-
expect(path).toBe('/
|
|
381
|
+
expect(path).toBe('/get-user-by-id/1');
|
|
305
382
|
});
|
|
306
383
|
test('version number included in path', () => {
|
|
307
384
|
const pathV1 = builder.makeRPCHttpRoutePath({ name: 'test', version: 1 });
|
|
308
385
|
const pathV2 = builder.makeRPCHttpRoutePath({ name: 'test', version: 2 });
|
|
309
386
|
const pathV99 = builder.makeRPCHttpRoutePath({ name: 'test', version: 99 });
|
|
310
|
-
expect(pathV1).toBe('/
|
|
311
|
-
expect(pathV2).toBe('/
|
|
312
|
-
expect(pathV99).toBe('/
|
|
387
|
+
expect(pathV1).toBe('/test/1');
|
|
388
|
+
expect(pathV2).toBe('/test/2');
|
|
389
|
+
expect(pathV99).toBe('/test/99');
|
|
313
390
|
});
|
|
314
391
|
test('handles mixed case in array segments', () => {
|
|
315
392
|
const path = builder.makeRPCHttpRoutePath({
|
|
316
393
|
name: ['UserModule', 'getActiveUsers'],
|
|
317
394
|
version: 1,
|
|
318
395
|
});
|
|
319
|
-
expect(path).toBe('/
|
|
396
|
+
expect(path).toBe('/user-module/get-active-users/1');
|
|
320
397
|
});
|
|
321
398
|
});
|
|
322
399
|
// --------------------------------------------------------------------------
|
|
@@ -331,10 +408,11 @@ describe('ExpressRPCAppBuilder', () => {
|
|
|
331
408
|
const paramsSchema = v.object({ id: v.string() });
|
|
332
409
|
const returnSchema = v.object({ name: v.string() });
|
|
333
410
|
const RPC = Procedures();
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
411
|
+
RPC.Create('GetUser', { name: 'users', version: 1, schema: { params: paramsSchema, returnType: returnSchema } }, async () => ({ name: 'test' }));
|
|
412
|
+
builder.register(RPC, () => ({}));
|
|
413
|
+
builder.build();
|
|
414
|
+
const doc = builder.docs[0];
|
|
415
|
+
expect(doc.path).toBe('/users/1');
|
|
338
416
|
expect(doc.method).toBe('post');
|
|
339
417
|
expect(doc.jsonSchema.body).toBeDefined();
|
|
340
418
|
expect(doc.jsonSchema.response).toBeDefined();
|
|
@@ -342,15 +420,17 @@ describe('ExpressRPCAppBuilder', () => {
|
|
|
342
420
|
test('omits body schema when no params defined', () => {
|
|
343
421
|
const RPC = Procedures();
|
|
344
422
|
RPC.Create('NoParams', { name: 'no-params', version: 1 }, async () => ({ ok: true }));
|
|
345
|
-
|
|
346
|
-
|
|
423
|
+
builder.register(RPC, () => ({}));
|
|
424
|
+
builder.build();
|
|
425
|
+
const doc = builder.docs[0];
|
|
347
426
|
expect(doc.jsonSchema.body).toBeUndefined();
|
|
348
427
|
});
|
|
349
428
|
test('omits response schema when no returnType defined', () => {
|
|
350
429
|
const RPC = Procedures();
|
|
351
430
|
RPC.Create('NoReturn', { name: 'no-return', version: 1, schema: { params: v.object({ x: v.number() }) } }, async () => ({}));
|
|
352
|
-
|
|
353
|
-
|
|
431
|
+
builder.register(RPC, () => ({}));
|
|
432
|
+
builder.build();
|
|
433
|
+
const doc = builder.docs[0];
|
|
354
434
|
expect(doc.jsonSchema.body).toBeDefined();
|
|
355
435
|
expect(doc.jsonSchema.response).toBeUndefined();
|
|
356
436
|
});
|
|
@@ -358,9 +438,9 @@ describe('ExpressRPCAppBuilder', () => {
|
|
|
358
438
|
const RPC = Procedures();
|
|
359
439
|
RPC.Create('Test1', { name: 't1', version: 1 }, async () => ({}));
|
|
360
440
|
RPC.Create('Test2', { name: 't2', version: 2 }, async () => ({}));
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
441
|
+
builder.register(RPC, () => ({}));
|
|
442
|
+
builder.build();
|
|
443
|
+
builder.docs.forEach((doc) => {
|
|
364
444
|
expect(doc.method).toBe('post');
|
|
365
445
|
});
|
|
366
446
|
});
|
|
@@ -406,21 +486,21 @@ describe('ExpressRPCAppBuilder', () => {
|
|
|
406
486
|
}));
|
|
407
487
|
const app = builder.build();
|
|
408
488
|
// Test public endpoints
|
|
409
|
-
const versionRes = await request(app).post('/
|
|
489
|
+
const versionRes = await request(app).post('/system/version/1').send({});
|
|
410
490
|
expect(versionRes.status).toBe(200);
|
|
411
491
|
expect(versionRes.body).toEqual({ version: '1.0.0' });
|
|
412
|
-
const healthRes = await request(app).post('/
|
|
492
|
+
const healthRes = await request(app).post('/health/1').send({});
|
|
413
493
|
expect(healthRes.status).toBe(200);
|
|
414
494
|
expect(healthRes.body).toEqual({ status: 'ok' });
|
|
415
495
|
// Test authenticated endpoints
|
|
416
496
|
const profileRes = await request(app)
|
|
417
|
-
.post('/
|
|
497
|
+
.post('/users/profile/1')
|
|
418
498
|
.set('X-User-Id', 'user-123')
|
|
419
499
|
.send({});
|
|
420
500
|
expect(profileRes.status).toBe(200);
|
|
421
501
|
expect(profileRes.body).toEqual({ userId: 'user-123', source: 'auth' });
|
|
422
502
|
const updateRes = await request(app)
|
|
423
|
-
.post('/
|
|
503
|
+
.post('/users/profile/2')
|
|
424
504
|
.set('X-User-Id', 'user-456')
|
|
425
505
|
.send({ name: 'John Doe' });
|
|
426
506
|
expect(updateRes.status).toBe(200);
|
|
@@ -428,10 +508,10 @@ describe('ExpressRPCAppBuilder', () => {
|
|
|
428
508
|
// Verify documentation
|
|
429
509
|
expect(builder.docs).toHaveLength(4);
|
|
430
510
|
const paths = builder.docs.map((d) => d.path);
|
|
431
|
-
expect(paths).toContain('/
|
|
432
|
-
expect(paths).toContain('/
|
|
433
|
-
expect(paths).toContain('/
|
|
434
|
-
expect(paths).toContain('/
|
|
511
|
+
expect(paths).toContain('/system/version/1');
|
|
512
|
+
expect(paths).toContain('/health/1');
|
|
513
|
+
expect(paths).toContain('/users/profile/1');
|
|
514
|
+
expect(paths).toContain('/users/profile/2');
|
|
435
515
|
// Verify hooks were called
|
|
436
516
|
expect(events).toContain('request-start');
|
|
437
517
|
expect(events).toContain('success:GetVersion');
|