@onebun/core 0.2.6 → 0.2.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +6 -6
- package/src/application/application.test.ts +350 -7
- package/src/application/application.ts +537 -254
- package/src/application/multi-service-application.test.ts +15 -0
- package/src/application/multi-service-application.ts +2 -0
- package/src/application/multi-service.types.ts +7 -1
- package/src/decorators/decorators.ts +213 -0
- package/src/docs-examples.test.ts +386 -3
- package/src/exception-filters/exception-filters.test.ts +172 -0
- package/src/exception-filters/exception-filters.ts +129 -0
- package/src/exception-filters/http-exception.ts +22 -0
- package/src/exception-filters/index.ts +2 -0
- package/src/file/onebun-file.ts +8 -2
- package/src/http-guards/http-guards.test.ts +230 -0
- package/src/http-guards/http-guards.ts +173 -0
- package/src/http-guards/index.ts +1 -0
- package/src/index.ts +10 -0
- package/src/module/module.test.ts +78 -0
- package/src/module/module.ts +55 -7
- package/src/queue/docs-examples.test.ts +72 -12
- package/src/queue/index.ts +4 -0
- package/src/queue/queue-service-proxy.test.ts +82 -0
- package/src/queue/queue-service-proxy.ts +114 -0
- package/src/queue/types.ts +2 -2
- package/src/security/cors-middleware.ts +212 -0
- package/src/security/index.ts +19 -0
- package/src/security/rate-limit-middleware.ts +276 -0
- package/src/security/security-headers-middleware.ts +188 -0
- package/src/security/security.test.ts +285 -0
- package/src/testing/index.ts +1 -0
- package/src/testing/testing-module.test.ts +199 -0
- package/src/testing/testing-module.ts +252 -0
- package/src/types.ts +153 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@onebun/core",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.8",
|
|
4
4
|
"description": "Core package for OneBun framework - decorators, DI, modules, controllers",
|
|
5
5
|
"license": "LGPL-3.0",
|
|
6
6
|
"author": "RemRyahirev",
|
|
@@ -41,11 +41,11 @@
|
|
|
41
41
|
"dependencies": {
|
|
42
42
|
"effect": "^3.13.10",
|
|
43
43
|
"arktype": "^2.0.0",
|
|
44
|
-
"@onebun/logger": "^0.2.
|
|
45
|
-
"@onebun/envs": "^0.2.
|
|
46
|
-
"@onebun/metrics": "^0.2.
|
|
47
|
-
"@onebun/requests": "^0.2.
|
|
48
|
-
"@onebun/trace": "^0.2.
|
|
44
|
+
"@onebun/logger": "^0.2.1",
|
|
45
|
+
"@onebun/envs": "^0.2.1",
|
|
46
|
+
"@onebun/metrics": "^0.2.1",
|
|
47
|
+
"@onebun/requests": "^0.2.1",
|
|
48
|
+
"@onebun/trace": "^0.2.1"
|
|
49
49
|
},
|
|
50
50
|
"devDependencies": {
|
|
51
51
|
"bun-types": "^1.3.8",
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
|
|
1
4
|
import { type as arktype } from 'arktype';
|
|
2
5
|
import {
|
|
3
6
|
describe,
|
|
@@ -10,7 +13,8 @@ import {
|
|
|
10
13
|
// eslint-disable-next-line import/no-extraneous-dependencies
|
|
11
14
|
import { register } from 'prom-client';
|
|
12
15
|
|
|
13
|
-
import type {
|
|
16
|
+
import type { QueueAdapter, Subscription } from '../queue/types';
|
|
17
|
+
import type { ApplicationOptions, ModuleInstance } from '../types';
|
|
14
18
|
import type {
|
|
15
19
|
MiddlewareClass,
|
|
16
20
|
OneBunRequest,
|
|
@@ -35,8 +39,11 @@ import {
|
|
|
35
39
|
import { Controller as BaseController } from '../module/controller';
|
|
36
40
|
import { BaseMiddleware } from '../module/middleware';
|
|
37
41
|
import { Service } from '../module/service';
|
|
42
|
+
import { QueueService, QUEUE_NOT_ENABLED_ERROR_MESSAGE } from '../queue';
|
|
43
|
+
import { Subscribe } from '../queue/decorators';
|
|
38
44
|
import { makeMockLoggerLayer } from '../testing/test-utils';
|
|
39
45
|
|
|
46
|
+
|
|
40
47
|
import { OneBunApplication } from './application';
|
|
41
48
|
|
|
42
49
|
// Helper function to create app with mock logger to suppress logs in tests
|
|
@@ -1835,7 +1842,7 @@ describe('OneBunApplication', () => {
|
|
|
1835
1842
|
expect(body.result.hasOptional).toBe(false);
|
|
1836
1843
|
});
|
|
1837
1844
|
|
|
1838
|
-
test('should return
|
|
1845
|
+
test('should return 400 when required query parameter is missing', async () => {
|
|
1839
1846
|
@Controller('/api')
|
|
1840
1847
|
class ApiController extends BaseController {
|
|
1841
1848
|
@Get('/required-query')
|
|
@@ -1862,7 +1869,7 @@ describe('OneBunApplication', () => {
|
|
|
1862
1869
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1863
1870
|
const response = await (mockServer as any).fetchHandler(request);
|
|
1864
1871
|
|
|
1865
|
-
expect(response.status).toBe(
|
|
1872
|
+
expect(response.status).toBe(400);
|
|
1866
1873
|
});
|
|
1867
1874
|
|
|
1868
1875
|
test('should pass validation with required query parameter present', async () => {
|
|
@@ -1959,7 +1966,7 @@ describe('OneBunApplication', () => {
|
|
|
1959
1966
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1960
1967
|
const response = await (mockServer as any).fetchHandler(request);
|
|
1961
1968
|
|
|
1962
|
-
expect(response.status).toBe(
|
|
1969
|
+
expect(response.status).toBe(400);
|
|
1963
1970
|
});
|
|
1964
1971
|
|
|
1965
1972
|
test('should validate body with arktype schema', async () => {
|
|
@@ -2038,7 +2045,7 @@ describe('OneBunApplication', () => {
|
|
|
2038
2045
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
2039
2046
|
const response = await (mockServer as any).fetchHandler(request);
|
|
2040
2047
|
|
|
2041
|
-
expect(response.status).toBe(
|
|
2048
|
+
expect(response.status).toBe(400);
|
|
2042
2049
|
});
|
|
2043
2050
|
|
|
2044
2051
|
test('should handle metrics endpoint', async () => {
|
|
@@ -2089,6 +2096,179 @@ describe('OneBunApplication', () => {
|
|
|
2089
2096
|
expect(response.status).toBe(404);
|
|
2090
2097
|
});
|
|
2091
2098
|
|
|
2099
|
+
describe('Static file serving', () => {
|
|
2100
|
+
test('should serve file from static root when static.root is set', async () => {
|
|
2101
|
+
const pathMod = await import('node:path');
|
|
2102
|
+
const tmpDir = fs.mkdtempSync(pathMod.join(fs.realpathSync(os.tmpdir()), 'onebun-static-'));
|
|
2103
|
+
try {
|
|
2104
|
+
const indexPath = pathMod.join(tmpDir, 'index.html');
|
|
2105
|
+
fs.writeFileSync(indexPath, '<html>Hello</html>', 'utf8');
|
|
2106
|
+
|
|
2107
|
+
@Module({})
|
|
2108
|
+
class TestModule {}
|
|
2109
|
+
|
|
2110
|
+
const app = createTestApp(TestModule, {
|
|
2111
|
+
static: { root: tmpDir },
|
|
2112
|
+
});
|
|
2113
|
+
await app.start();
|
|
2114
|
+
|
|
2115
|
+
const request = new Request('http://localhost:3000/index.html', { method: 'GET' });
|
|
2116
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
2117
|
+
const response = await (mockServer as any).fetchHandler(request);
|
|
2118
|
+
|
|
2119
|
+
expect(response.status).toBe(200);
|
|
2120
|
+
expect(await response.text()).toBe('<html>Hello</html>');
|
|
2121
|
+
} finally {
|
|
2122
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
2123
|
+
}
|
|
2124
|
+
});
|
|
2125
|
+
|
|
2126
|
+
test('should return 404 when static is not configured', async () => {
|
|
2127
|
+
@Module({})
|
|
2128
|
+
class TestModule {}
|
|
2129
|
+
|
|
2130
|
+
const app = createTestApp(TestModule);
|
|
2131
|
+
await app.start();
|
|
2132
|
+
|
|
2133
|
+
const request = new Request('http://localhost:3000/', { method: 'GET' });
|
|
2134
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
2135
|
+
const response = await (mockServer as any).fetchHandler(request);
|
|
2136
|
+
|
|
2137
|
+
expect(response.status).toBe(404);
|
|
2138
|
+
});
|
|
2139
|
+
|
|
2140
|
+
test('should respect pathPrefix and serve only under prefix', async () => {
|
|
2141
|
+
const pathMod = await import('node:path');
|
|
2142
|
+
const tmpDir = fs.mkdtempSync(pathMod.join(fs.realpathSync(os.tmpdir()), 'onebun-static-'));
|
|
2143
|
+
try {
|
|
2144
|
+
fs.writeFileSync(pathMod.join(tmpDir, 'foo.html'), '<html>Foo</html>', 'utf8');
|
|
2145
|
+
|
|
2146
|
+
@Module({})
|
|
2147
|
+
class TestModule {}
|
|
2148
|
+
|
|
2149
|
+
const app = createTestApp(TestModule, {
|
|
2150
|
+
static: { root: tmpDir, pathPrefix: '/app' },
|
|
2151
|
+
});
|
|
2152
|
+
await app.start();
|
|
2153
|
+
|
|
2154
|
+
const reqUnderPrefix = new Request('http://localhost:3000/app/foo.html', { method: 'GET' });
|
|
2155
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
2156
|
+
const resUnder = await (mockServer as any).fetchHandler(reqUnderPrefix);
|
|
2157
|
+
expect(resUnder.status).toBe(200);
|
|
2158
|
+
expect(await resUnder.text()).toBe('<html>Foo</html>');
|
|
2159
|
+
|
|
2160
|
+
const reqOutsidePrefix = new Request('http://localhost:3000/foo.html', { method: 'GET' });
|
|
2161
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
2162
|
+
const resOutside = await (mockServer as any).fetchHandler(reqOutsidePrefix);
|
|
2163
|
+
expect(resOutside.status).toBe(404);
|
|
2164
|
+
} finally {
|
|
2165
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
2166
|
+
}
|
|
2167
|
+
});
|
|
2168
|
+
|
|
2169
|
+
test('should return fallbackFile for missing path when fallbackFile is set', async () => {
|
|
2170
|
+
const pathMod = await import('node:path');
|
|
2171
|
+
const tmpDir = fs.mkdtempSync(pathMod.join(fs.realpathSync(os.tmpdir()), 'onebun-static-'));
|
|
2172
|
+
try {
|
|
2173
|
+
fs.writeFileSync(pathMod.join(tmpDir, 'index.html'), '<html>SPA</html>', 'utf8');
|
|
2174
|
+
|
|
2175
|
+
@Module({})
|
|
2176
|
+
class TestModule {}
|
|
2177
|
+
|
|
2178
|
+
const app = createTestApp(TestModule, {
|
|
2179
|
+
static: { root: tmpDir, fallbackFile: 'index.html' },
|
|
2180
|
+
});
|
|
2181
|
+
await app.start();
|
|
2182
|
+
|
|
2183
|
+
const request = new Request('http://localhost:3000/any/client/route', { method: 'GET' });
|
|
2184
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
2185
|
+
const response = await (mockServer as any).fetchHandler(request);
|
|
2186
|
+
|
|
2187
|
+
expect(response.status).toBe(200);
|
|
2188
|
+
expect(await response.text()).toBe('<html>SPA</html>');
|
|
2189
|
+
} finally {
|
|
2190
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
2191
|
+
}
|
|
2192
|
+
});
|
|
2193
|
+
|
|
2194
|
+
test('should reject path traversal and return 404', async () => {
|
|
2195
|
+
const pathMod = await import('node:path');
|
|
2196
|
+
const tmpDir = fs.mkdtempSync(pathMod.join(fs.realpathSync(os.tmpdir()), 'onebun-static-'));
|
|
2197
|
+
try {
|
|
2198
|
+
fs.writeFileSync(pathMod.join(tmpDir, 'safe.txt'), 'safe', 'utf8');
|
|
2199
|
+
|
|
2200
|
+
@Module({})
|
|
2201
|
+
class TestModule {}
|
|
2202
|
+
|
|
2203
|
+
const app = createTestApp(TestModule, {
|
|
2204
|
+
static: { root: tmpDir },
|
|
2205
|
+
});
|
|
2206
|
+
await app.start();
|
|
2207
|
+
|
|
2208
|
+
const request = new Request('http://localhost:3000/../etc/passwd', { method: 'GET' });
|
|
2209
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
2210
|
+
const response = await (mockServer as any).fetchHandler(request);
|
|
2211
|
+
|
|
2212
|
+
expect(response.status).toBe(404);
|
|
2213
|
+
} finally {
|
|
2214
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
2215
|
+
}
|
|
2216
|
+
});
|
|
2217
|
+
|
|
2218
|
+
test('should cache file existence and serve from cache on second request', async () => {
|
|
2219
|
+
const pathMod = await import('node:path');
|
|
2220
|
+
const tmpDir = fs.mkdtempSync(pathMod.join(fs.realpathSync(os.tmpdir()), 'onebun-static-'));
|
|
2221
|
+
try {
|
|
2222
|
+
const filePath = pathMod.join(tmpDir, 'cached.html');
|
|
2223
|
+
fs.writeFileSync(filePath, '<html>Cached</html>', 'utf8');
|
|
2224
|
+
|
|
2225
|
+
@Module({})
|
|
2226
|
+
class TestModule {}
|
|
2227
|
+
|
|
2228
|
+
const app = createTestApp(TestModule, {
|
|
2229
|
+
static: { root: tmpDir, fileExistenceCacheTtlMs: 60_000 },
|
|
2230
|
+
});
|
|
2231
|
+
await app.start();
|
|
2232
|
+
|
|
2233
|
+
const request = new Request('http://localhost:3000/cached.html', { method: 'GET' });
|
|
2234
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
2235
|
+
const response1 = await (mockServer as any).fetchHandler(request);
|
|
2236
|
+
expect(response1.status).toBe(200);
|
|
2237
|
+
expect(await response1.text()).toBe('<html>Cached</html>');
|
|
2238
|
+
|
|
2239
|
+
// Second request should still return the file (cache hit; no extra disk read for existence)
|
|
2240
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
2241
|
+
const response2 = await (mockServer as any).fetchHandler(request);
|
|
2242
|
+
expect(response2.status).toBe(200);
|
|
2243
|
+
expect(await response2.text()).toBe('<html>Cached</html>');
|
|
2244
|
+
} finally {
|
|
2245
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
2246
|
+
}
|
|
2247
|
+
});
|
|
2248
|
+
|
|
2249
|
+
test('should not serve static for POST', async () => {
|
|
2250
|
+
const pathMod = await import('node:path');
|
|
2251
|
+
const tmpDir = fs.mkdtempSync(pathMod.join(fs.realpathSync(os.tmpdir()), 'onebun-static-'));
|
|
2252
|
+
try {
|
|
2253
|
+
fs.writeFileSync(pathMod.join(tmpDir, 'index.html'), '<html>Hi</html>', 'utf8');
|
|
2254
|
+
|
|
2255
|
+
@Module({})
|
|
2256
|
+
class TestModule {}
|
|
2257
|
+
|
|
2258
|
+
const app = createTestApp(TestModule, { static: { root: tmpDir } });
|
|
2259
|
+
await app.start();
|
|
2260
|
+
|
|
2261
|
+
const request = new Request('http://localhost:3000/index.html', { method: 'POST' });
|
|
2262
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
2263
|
+
const response = await (mockServer as any).fetchHandler(request);
|
|
2264
|
+
|
|
2265
|
+
expect(response.status).toBe(404);
|
|
2266
|
+
} finally {
|
|
2267
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
2268
|
+
}
|
|
2269
|
+
});
|
|
2270
|
+
});
|
|
2271
|
+
|
|
2092
2272
|
test('should handle method not allowed', async () => {
|
|
2093
2273
|
@Controller('/api')
|
|
2094
2274
|
class ApiController extends BaseController {
|
|
@@ -3269,7 +3449,7 @@ describe('OneBunApplication', () => {
|
|
|
3269
3449
|
expect(body.result.isUndefined).toBe(true);
|
|
3270
3450
|
});
|
|
3271
3451
|
|
|
3272
|
-
test('should return
|
|
3452
|
+
test('should return 400 when required cookie is missing', async () => {
|
|
3273
3453
|
@Controller('/api')
|
|
3274
3454
|
class ApiController extends BaseController {
|
|
3275
3455
|
@Get('/auth')
|
|
@@ -3294,7 +3474,7 @@ describe('OneBunApplication', () => {
|
|
|
3294
3474
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
3295
3475
|
const response = await (mockServer as any).fetchHandler(request);
|
|
3296
3476
|
|
|
3297
|
-
expect(response.status).toBe(
|
|
3477
|
+
expect(response.status).toBe(400);
|
|
3298
3478
|
});
|
|
3299
3479
|
|
|
3300
3480
|
test('should extract multiple cookies with @Cookie decorator', async () => {
|
|
@@ -3694,4 +3874,167 @@ describe('OneBunApplication', () => {
|
|
|
3694
3874
|
await app.stop();
|
|
3695
3875
|
});
|
|
3696
3876
|
});
|
|
3877
|
+
|
|
3878
|
+
describe('queue custom adapter', () => {
|
|
3879
|
+
/** Mock adapter that records constructor options for testing */
|
|
3880
|
+
/* eslint-disable @typescript-eslint/no-empty-function */
|
|
3881
|
+
class MockCustomAdapter implements QueueAdapter {
|
|
3882
|
+
static receivedOptions: unknown = null;
|
|
3883
|
+
readonly name = 'mock-custom';
|
|
3884
|
+
readonly type = 'jetstream';
|
|
3885
|
+
private connected = false;
|
|
3886
|
+
|
|
3887
|
+
constructor(options?: unknown) {
|
|
3888
|
+
(this.constructor as typeof MockCustomAdapter).receivedOptions = options;
|
|
3889
|
+
}
|
|
3890
|
+
|
|
3891
|
+
async connect(): Promise<void> {
|
|
3892
|
+
this.connected = true;
|
|
3893
|
+
}
|
|
3894
|
+
|
|
3895
|
+
async disconnect(): Promise<void> {
|
|
3896
|
+
this.connected = false;
|
|
3897
|
+
}
|
|
3898
|
+
|
|
3899
|
+
isConnected(): boolean {
|
|
3900
|
+
return this.connected;
|
|
3901
|
+
}
|
|
3902
|
+
|
|
3903
|
+
async publish(): Promise<string> {
|
|
3904
|
+
return 'mock-id';
|
|
3905
|
+
}
|
|
3906
|
+
|
|
3907
|
+
async publishBatch(): Promise<string[]> {
|
|
3908
|
+
return [];
|
|
3909
|
+
}
|
|
3910
|
+
|
|
3911
|
+
async subscribe(): Promise<Subscription> {
|
|
3912
|
+
return {
|
|
3913
|
+
async unsubscribe() {},
|
|
3914
|
+
pause() {},
|
|
3915
|
+
resume() {},
|
|
3916
|
+
pattern: '',
|
|
3917
|
+
isActive: true,
|
|
3918
|
+
};
|
|
3919
|
+
}
|
|
3920
|
+
|
|
3921
|
+
async addScheduledJob(): Promise<void> {}
|
|
3922
|
+
async removeScheduledJob(): Promise<boolean> {
|
|
3923
|
+
return false;
|
|
3924
|
+
}
|
|
3925
|
+
async getScheduledJobs(): Promise<import('../queue/types').ScheduledJobInfo[]> {
|
|
3926
|
+
return [];
|
|
3927
|
+
}
|
|
3928
|
+
supports(): boolean {
|
|
3929
|
+
return false;
|
|
3930
|
+
}
|
|
3931
|
+
on(): void {}
|
|
3932
|
+
off(): void {}
|
|
3933
|
+
}
|
|
3934
|
+
/* eslint-enable @typescript-eslint/no-empty-function */
|
|
3935
|
+
|
|
3936
|
+
@Controller('/q')
|
|
3937
|
+
class QueueTestController extends BaseController {
|
|
3938
|
+
@Subscribe('test.event')
|
|
3939
|
+
async handle(): Promise<void> {}
|
|
3940
|
+
}
|
|
3941
|
+
|
|
3942
|
+
@Module({
|
|
3943
|
+
controllers: [QueueTestController],
|
|
3944
|
+
providers: [],
|
|
3945
|
+
})
|
|
3946
|
+
class QueueTestModule {}
|
|
3947
|
+
|
|
3948
|
+
beforeEach(() => {
|
|
3949
|
+
MockCustomAdapter.receivedOptions = null;
|
|
3950
|
+
});
|
|
3951
|
+
|
|
3952
|
+
test('should initialize queue with custom adapter constructor and options', async () => {
|
|
3953
|
+
const app = createTestApp(QueueTestModule, {
|
|
3954
|
+
port: 0,
|
|
3955
|
+
queue: {
|
|
3956
|
+
enabled: true,
|
|
3957
|
+
adapter: MockCustomAdapter,
|
|
3958
|
+
options: { servers: 'nats://localhost:4222' },
|
|
3959
|
+
},
|
|
3960
|
+
});
|
|
3961
|
+
await app.start();
|
|
3962
|
+
|
|
3963
|
+
const queueService = app.getQueueService();
|
|
3964
|
+
expect(queueService).not.toBeNull();
|
|
3965
|
+
expect(MockCustomAdapter.receivedOptions).toEqual({
|
|
3966
|
+
servers: 'nats://localhost:4222',
|
|
3967
|
+
});
|
|
3968
|
+
|
|
3969
|
+
await app.stop();
|
|
3970
|
+
});
|
|
3971
|
+
});
|
|
3972
|
+
|
|
3973
|
+
describe('QueueService DI injection', () => {
|
|
3974
|
+
@Controller('/queue-di')
|
|
3975
|
+
class QueueDiTestController extends BaseController {
|
|
3976
|
+
constructor(private queueService: QueueService) {
|
|
3977
|
+
super();
|
|
3978
|
+
}
|
|
3979
|
+
|
|
3980
|
+
/** Exposed for tests: call publish on injected QueueService */
|
|
3981
|
+
publishTest(pattern: string, data: unknown) {
|
|
3982
|
+
return this.queueService.publish(pattern, data);
|
|
3983
|
+
}
|
|
3984
|
+
}
|
|
3985
|
+
|
|
3986
|
+
@Controller('/q')
|
|
3987
|
+
class QueueSubscribeController extends BaseController {
|
|
3988
|
+
@Subscribe('di.test')
|
|
3989
|
+
async handle(): Promise<void> {}
|
|
3990
|
+
}
|
|
3991
|
+
|
|
3992
|
+
@Module({
|
|
3993
|
+
controllers: [QueueDiTestController],
|
|
3994
|
+
providers: [],
|
|
3995
|
+
})
|
|
3996
|
+
class QueueDisabledModule {}
|
|
3997
|
+
|
|
3998
|
+
@Module({
|
|
3999
|
+
controllers: [QueueDiTestController, QueueSubscribeController],
|
|
4000
|
+
providers: [],
|
|
4001
|
+
})
|
|
4002
|
+
class QueueEnabledModule {}
|
|
4003
|
+
|
|
4004
|
+
test('when queue is enabled, controller with injected QueueService gets working instance', async () => {
|
|
4005
|
+
const app = createTestApp(QueueEnabledModule, {
|
|
4006
|
+
port: 0,
|
|
4007
|
+
queue: {
|
|
4008
|
+
enabled: true,
|
|
4009
|
+
adapter: 'memory',
|
|
4010
|
+
},
|
|
4011
|
+
});
|
|
4012
|
+
await app.start();
|
|
4013
|
+
|
|
4014
|
+
const rootModule = (app as unknown as { rootModule: ModuleInstance }).rootModule;
|
|
4015
|
+
const controller = rootModule.getControllerInstance?.(QueueDiTestController) as
|
|
4016
|
+
| QueueDiTestController
|
|
4017
|
+
| undefined;
|
|
4018
|
+
expect(controller).toBeDefined();
|
|
4019
|
+
await expect(controller!.publishTest('di.test', {})).resolves.toBeDefined();
|
|
4020
|
+
|
|
4021
|
+
await app.stop();
|
|
4022
|
+
});
|
|
4023
|
+
|
|
4024
|
+
test('when queue is disabled, injected QueueService throws on first method call', async () => {
|
|
4025
|
+
const app = createTestApp(QueueDisabledModule, { port: 0 });
|
|
4026
|
+
await app.start();
|
|
4027
|
+
|
|
4028
|
+
const rootModule = (app as unknown as { rootModule: ModuleInstance }).rootModule;
|
|
4029
|
+
const controller = rootModule.getControllerInstance?.(QueueDiTestController) as
|
|
4030
|
+
| QueueDiTestController
|
|
4031
|
+
| undefined;
|
|
4032
|
+
expect(controller).toBeDefined();
|
|
4033
|
+
await expect(controller!.publishTest('e', {})).rejects.toThrow(
|
|
4034
|
+
QUEUE_NOT_ENABLED_ERROR_MESSAGE,
|
|
4035
|
+
);
|
|
4036
|
+
|
|
4037
|
+
await app.stop();
|
|
4038
|
+
});
|
|
4039
|
+
});
|
|
3697
4040
|
});
|