@qwickapps/server 1.1.6 → 1.1.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/core/control-panel.d.ts.map +1 -1
- package/dist/core/control-panel.js +5 -8
- package/dist/core/control-panel.js.map +1 -1
- package/dist/core/gateway.d.ts.map +1 -1
- package/dist/core/gateway.js +11 -23
- package/dist/core/gateway.js.map +1 -1
- package/dist/core/health-manager.d.ts.map +1 -1
- package/dist/core/health-manager.js +3 -9
- package/dist/core/health-manager.js.map +1 -1
- package/dist/core/logging.d.ts.map +1 -1
- package/dist/core/logging.js +1 -5
- package/dist/core/logging.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -1
- package/dist/index.js.map +1 -1
- package/dist/plugins/cache-plugin.d.ts +219 -0
- package/dist/plugins/cache-plugin.d.ts.map +1 -0
- package/dist/plugins/cache-plugin.js +326 -0
- package/dist/plugins/cache-plugin.js.map +1 -0
- package/dist/plugins/cache-plugin.test.d.ts +8 -0
- package/dist/plugins/cache-plugin.test.d.ts.map +1 -0
- package/dist/plugins/cache-plugin.test.js +188 -0
- package/dist/plugins/cache-plugin.test.js.map +1 -0
- package/dist/plugins/config-plugin.js +1 -1
- package/dist/plugins/config-plugin.js.map +1 -1
- package/dist/plugins/diagnostics-plugin.js +1 -1
- package/dist/plugins/diagnostics-plugin.js.map +1 -1
- package/dist/plugins/health-plugin.js +1 -1
- package/dist/plugins/health-plugin.js.map +1 -1
- package/dist/plugins/index.d.ts +6 -0
- package/dist/plugins/index.d.ts.map +1 -1
- package/dist/plugins/index.js +4 -0
- package/dist/plugins/index.js.map +1 -1
- package/dist/plugins/logs-plugin.d.ts.map +1 -1
- package/dist/plugins/logs-plugin.js +1 -3
- package/dist/plugins/logs-plugin.js.map +1 -1
- package/dist/plugins/postgres-plugin.d.ts +155 -0
- package/dist/plugins/postgres-plugin.d.ts.map +1 -0
- package/dist/plugins/postgres-plugin.js +244 -0
- package/dist/plugins/postgres-plugin.js.map +1 -0
- package/dist/plugins/postgres-plugin.test.d.ts +8 -0
- package/dist/plugins/postgres-plugin.test.d.ts.map +1 -0
- package/dist/plugins/postgres-plugin.test.js +165 -0
- package/dist/plugins/postgres-plugin.test.js.map +1 -0
- package/dist-ui/assets/{index-Bk7ypbI4.js → index-CW1BviRn.js} +2 -2
- package/dist-ui/assets/{index-Bk7ypbI4.js.map → index-CW1BviRn.js.map} +1 -1
- package/dist-ui/index.html +1 -1
- package/package.json +13 -2
- package/src/core/control-panel.ts +5 -8
- package/src/core/gateway.ts +12 -24
- package/src/core/health-manager.ts +3 -9
- package/src/core/logging.ts +1 -5
- package/src/index.ts +22 -0
- package/src/plugins/cache-plugin.test.ts +241 -0
- package/src/plugins/cache-plugin.ts +503 -0
- package/src/plugins/config-plugin.ts +1 -1
- package/src/plugins/diagnostics-plugin.ts +1 -1
- package/src/plugins/health-plugin.ts +1 -1
- package/src/plugins/index.ts +10 -0
- package/src/plugins/logs-plugin.ts +1 -3
- package/src/plugins/postgres-plugin.test.ts +213 -0
- package/src/plugins/postgres-plugin.ts +345 -0
- package/ui/src/api/controlPanelApi.ts +1 -1
- package/ui/src/pages/LogsPage.tsx +6 -10
package/dist-ui/index.html
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
6
|
<title>Control Panel</title>
|
|
7
|
-
<script type="module" crossorigin src="/assets/index-
|
|
7
|
+
<script type="module" crossorigin src="/assets/index-CW1BviRn.js"></script>
|
|
8
8
|
<link rel="stylesheet" crossorigin href="/assets/index-CiizQQnb.css">
|
|
9
9
|
</head>
|
|
10
10
|
<body>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@qwickapps/server",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.7",
|
|
4
4
|
"description": "Plugin-based application server framework for building websites, APIs, admin dashboards, and full-stack products",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -55,7 +55,10 @@
|
|
|
55
55
|
"@types/cors": "^2.8.17",
|
|
56
56
|
"@types/express": "^4.17.21",
|
|
57
57
|
"@types/node": "^20.10.5",
|
|
58
|
+
"@types/pg": "^8.11.0",
|
|
58
59
|
"@types/react": "^18.2.0",
|
|
60
|
+
"ioredis": "^5.4.0",
|
|
61
|
+
"pg": "^8.13.0",
|
|
59
62
|
"@types/react-dom": "^18.2.0",
|
|
60
63
|
"@vitejs/plugin-react": "^4.3.4",
|
|
61
64
|
"express-openid-connect": "^2.19.3",
|
|
@@ -68,7 +71,9 @@
|
|
|
68
71
|
},
|
|
69
72
|
"peerDependencies": {
|
|
70
73
|
"@qwickapps/react-framework": ">=1.0.0",
|
|
71
|
-
"express-openid-connect": ">=2.0.0"
|
|
74
|
+
"express-openid-connect": ">=2.0.0",
|
|
75
|
+
"ioredis": ">=5.0.0",
|
|
76
|
+
"pg": ">=8.0.0"
|
|
72
77
|
},
|
|
73
78
|
"peerDependenciesMeta": {
|
|
74
79
|
"@qwickapps/react-framework": {
|
|
@@ -76,6 +81,12 @@
|
|
|
76
81
|
},
|
|
77
82
|
"express-openid-connect": {
|
|
78
83
|
"optional": true
|
|
84
|
+
},
|
|
85
|
+
"ioredis": {
|
|
86
|
+
"optional": true
|
|
87
|
+
},
|
|
88
|
+
"pg": {
|
|
89
|
+
"optional": true
|
|
79
90
|
}
|
|
80
91
|
},
|
|
81
92
|
"keywords": [
|
|
@@ -169,7 +169,7 @@ export function createControlPanel(options: CreateControlPanelOptions): ControlP
|
|
|
169
169
|
logger.debug(`Dashboard config: mountPath=${mountPath}, effectiveUiPath=${effectiveUiPath}, hasRichUI=${hasRichUI}, useRichUI=${useRichUI}`);
|
|
170
170
|
|
|
171
171
|
if (useRichUI) {
|
|
172
|
-
logger.
|
|
172
|
+
logger.debug(`Serving React UI from ${effectiveUiPath}`);
|
|
173
173
|
// Serve static assets from dist-ui at the mount path
|
|
174
174
|
app.use(mountPath, express.static(effectiveUiPath));
|
|
175
175
|
|
|
@@ -189,7 +189,7 @@ export function createControlPanel(options: CreateControlPanelOptions): ControlP
|
|
|
189
189
|
});
|
|
190
190
|
}
|
|
191
191
|
} else {
|
|
192
|
-
logger.
|
|
192
|
+
logger.debug(`Serving basic HTML dashboard`);
|
|
193
193
|
const dashboardPath = mountPath === '/' ? '/' : mountPath;
|
|
194
194
|
app.get(dashboardPath, (_req: Request, res: Response) => {
|
|
195
195
|
const html = generateDashboardHtml(config, healthManager.getResults(), mountPath);
|
|
@@ -209,7 +209,7 @@ export function createControlPanel(options: CreateControlPanelOptions): ControlP
|
|
|
209
209
|
|
|
210
210
|
// Register plugin
|
|
211
211
|
const registerPlugin = async (plugin: ControlPanelPlugin): Promise<void> => {
|
|
212
|
-
logger.
|
|
212
|
+
logger.debug(`Registering plugin: ${plugin.name}`);
|
|
213
213
|
|
|
214
214
|
// Register routes
|
|
215
215
|
if (plugin.routes) {
|
|
@@ -238,7 +238,7 @@ export function createControlPanel(options: CreateControlPanelOptions): ControlP
|
|
|
238
238
|
}
|
|
239
239
|
|
|
240
240
|
registeredPlugins.push(plugin);
|
|
241
|
-
logger.
|
|
241
|
+
logger.debug(`Plugin registered: ${plugin.name}`);
|
|
242
242
|
};
|
|
243
243
|
|
|
244
244
|
// Get diagnostics report
|
|
@@ -276,10 +276,7 @@ export function createControlPanel(options: CreateControlPanelOptions): ControlP
|
|
|
276
276
|
|
|
277
277
|
return new Promise((resolve) => {
|
|
278
278
|
server = app.listen(config.port, () => {
|
|
279
|
-
logger.info(`Control panel
|
|
280
|
-
logger.info(`Dashboard: http://localhost:${config.port}${mountPath}`);
|
|
281
|
-
logger.info(`Health: http://localhost:${config.port}${apiBasePath}/health`);
|
|
282
|
-
logger.info(`Diagnostics: http://localhost:${config.port}${apiBasePath}/diagnostics`);
|
|
279
|
+
logger.info(`Control panel listening on port ${config.port}`);
|
|
283
280
|
resolve();
|
|
284
281
|
});
|
|
285
282
|
});
|
package/src/core/gateway.ts
CHANGED
|
@@ -441,39 +441,27 @@ export function createGateway(
|
|
|
441
441
|
const apiBasePath = controlPanelPath === '/' ? '/api' : `${controlPanelPath}/api`;
|
|
442
442
|
|
|
443
443
|
// Log startup info
|
|
444
|
-
logger.info(
|
|
445
|
-
logger.info(
|
|
446
|
-
logger.info(` ${
|
|
447
|
-
|
|
448
|
-
logger.info('');
|
|
449
|
-
logger.info(` Gateway Port: ${gatewayPort} (public)`);
|
|
450
|
-
logger.info(` Service Port: ${servicePort} (internal)`);
|
|
451
|
-
logger.info('');
|
|
452
|
-
|
|
444
|
+
logger.info(`${config.productName} Gateway`);
|
|
445
|
+
logger.info(`Gateway Port: ${gatewayPort} (public)`);
|
|
446
|
+
logger.info(`Service Port: ${servicePort} (internal)`);
|
|
447
|
+
|
|
453
448
|
if (guardConfig && guardConfig.type === 'basic') {
|
|
454
|
-
logger.info(
|
|
455
|
-
logger.info(' ----------------------------------------');
|
|
456
|
-
logger.info(` Username: ${guardConfig.username}`);
|
|
457
|
-
logger.info(' ----------------------------------------');
|
|
449
|
+
logger.info(`Control Panel Auth: HTTP Basic Auth - Username: ${guardConfig.username}`);
|
|
458
450
|
} else if (guardConfig && guardConfig.type !== 'none') {
|
|
459
|
-
logger.info(`
|
|
451
|
+
logger.info(`Control Panel Auth: ${guardConfig.type}`);
|
|
460
452
|
} else {
|
|
461
|
-
logger.info('
|
|
453
|
+
logger.info('Control Panel Auth: None (not recommended)');
|
|
462
454
|
}
|
|
463
455
|
|
|
464
|
-
logger.info('');
|
|
465
|
-
logger.info(' Endpoints:');
|
|
466
456
|
if (config.frontendApp) {
|
|
467
|
-
logger.info(`
|
|
457
|
+
logger.info(`Frontend App: GET /`);
|
|
468
458
|
}
|
|
469
|
-
logger.info(`
|
|
470
|
-
logger.info(`
|
|
471
|
-
logger.info(`
|
|
459
|
+
logger.info(`Control Panel UI: GET ${controlPanelPath.padEnd(20)}`);
|
|
460
|
+
logger.info(`Gateway Health: GET ${apiBasePath}/health`);
|
|
461
|
+
logger.info(`Service Health: GET /health`);
|
|
472
462
|
for (const apiPath of proxyPaths) {
|
|
473
|
-
logger.info(`
|
|
463
|
+
logger.info(`Service API: * ${apiPath}/*`);
|
|
474
464
|
}
|
|
475
|
-
logger.info('========================================');
|
|
476
|
-
logger.info('');
|
|
477
465
|
};
|
|
478
466
|
|
|
479
467
|
const stop = async (): Promise<void> => {
|
|
@@ -40,10 +40,7 @@ export class HealthManager {
|
|
|
40
40
|
|
|
41
41
|
this.intervals.set(check.name, timer);
|
|
42
42
|
|
|
43
|
-
this.logger.
|
|
44
|
-
type: check.type,
|
|
45
|
-
interval,
|
|
46
|
-
});
|
|
43
|
+
this.logger.debug(`Health check registered: ${check.name} (${check.type}, ${interval}ms)`)
|
|
47
44
|
}
|
|
48
45
|
|
|
49
46
|
/**
|
|
@@ -101,10 +98,7 @@ export class HealthManager {
|
|
|
101
98
|
lastChecked: new Date(),
|
|
102
99
|
});
|
|
103
100
|
|
|
104
|
-
this.logger.warn(`
|
|
105
|
-
error: message,
|
|
106
|
-
latency,
|
|
107
|
-
});
|
|
101
|
+
this.logger.warn(`Health check failed: ${name} - ${message}`);
|
|
108
102
|
}
|
|
109
103
|
}
|
|
110
104
|
|
|
@@ -222,6 +216,6 @@ export class HealthManager {
|
|
|
222
216
|
clearInterval(timer);
|
|
223
217
|
}
|
|
224
218
|
this.intervals.clear();
|
|
225
|
-
this.logger.
|
|
219
|
+
this.logger.debug('Health manager shutdown complete');
|
|
226
220
|
}
|
|
227
221
|
}
|
package/src/core/logging.ts
CHANGED
|
@@ -135,12 +135,8 @@ class LoggingSubsystem {
|
|
|
135
135
|
}
|
|
136
136
|
|
|
137
137
|
this.initialized = true;
|
|
138
|
-
this.rootLogger.
|
|
139
|
-
logDir: this.config.logDir,
|
|
138
|
+
this.rootLogger.debug('Logging initialized', {
|
|
140
139
|
level: this.config.level,
|
|
141
|
-
fileLogging: this.config.fileLogging,
|
|
142
|
-
consoleOutput: this.config.consoleOutput,
|
|
143
|
-
usingPino: this.rootLogger.isUsingPino(),
|
|
144
140
|
});
|
|
145
141
|
}
|
|
146
142
|
|
package/src/index.ts
CHANGED
|
@@ -63,6 +63,18 @@ export {
|
|
|
63
63
|
createConfigPlugin,
|
|
64
64
|
createDiagnosticsPlugin,
|
|
65
65
|
createFrontendAppPlugin,
|
|
66
|
+
// Database plugins
|
|
67
|
+
createPostgresPlugin,
|
|
68
|
+
getPostgres,
|
|
69
|
+
hasPostgres,
|
|
70
|
+
// Backward compatibility aliases (deprecated)
|
|
71
|
+
createPostgresPlugin as createDatabasePlugin,
|
|
72
|
+
getPostgres as getDatabase,
|
|
73
|
+
hasPostgres as hasDatabase,
|
|
74
|
+
// Cache plugins
|
|
75
|
+
createCachePlugin,
|
|
76
|
+
getCache,
|
|
77
|
+
hasCache,
|
|
66
78
|
} from './plugins/index.js';
|
|
67
79
|
export type {
|
|
68
80
|
HealthPluginConfig,
|
|
@@ -70,4 +82,14 @@ export type {
|
|
|
70
82
|
ConfigPluginConfig,
|
|
71
83
|
DiagnosticsPluginConfig,
|
|
72
84
|
FrontendAppPluginConfig,
|
|
85
|
+
// Database plugin types
|
|
86
|
+
PostgresPluginConfig,
|
|
87
|
+
PostgresInstance,
|
|
88
|
+
TransactionCallback,
|
|
89
|
+
// Backward compatibility aliases (deprecated)
|
|
90
|
+
PostgresPluginConfig as DatabasePluginConfig,
|
|
91
|
+
PostgresInstance as DatabaseInstance,
|
|
92
|
+
// Cache plugin types
|
|
93
|
+
CachePluginConfig,
|
|
94
|
+
CacheInstance,
|
|
73
95
|
} from './plugins/index.js';
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cache Plugin Tests
|
|
3
|
+
*
|
|
4
|
+
* Note: These tests use mocks since we don't want to require a real Redis instance.
|
|
5
|
+
* Integration tests should be run separately with a real Redis instance.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
9
|
+
|
|
10
|
+
// Mock ioredis before importing the plugin
|
|
11
|
+
vi.mock('ioredis', () => {
|
|
12
|
+
const mockClient = {
|
|
13
|
+
get: vi.fn().mockResolvedValue(null),
|
|
14
|
+
setex: vi.fn().mockResolvedValue('OK'),
|
|
15
|
+
del: vi.fn().mockResolvedValue(1),
|
|
16
|
+
exists: vi.fn().mockResolvedValue(1),
|
|
17
|
+
expire: vi.fn().mockResolvedValue(1),
|
|
18
|
+
ttl: vi.fn().mockResolvedValue(3600),
|
|
19
|
+
incr: vi.fn().mockResolvedValue(1),
|
|
20
|
+
incrby: vi.fn().mockResolvedValue(5),
|
|
21
|
+
keys: vi.fn().mockResolvedValue([]),
|
|
22
|
+
info: vi.fn().mockResolvedValue('used_memory_human:1.5M\n'),
|
|
23
|
+
dbsize: vi.fn().mockResolvedValue(100),
|
|
24
|
+
ping: vi.fn().mockResolvedValue('PONG'),
|
|
25
|
+
quit: vi.fn().mockResolvedValue('OK'),
|
|
26
|
+
on: vi.fn(),
|
|
27
|
+
status: 'ready',
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
return {
|
|
31
|
+
default: vi.fn(() => mockClient),
|
|
32
|
+
};
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
import {
|
|
36
|
+
createCachePlugin,
|
|
37
|
+
getCache,
|
|
38
|
+
hasCache,
|
|
39
|
+
type CachePluginConfig,
|
|
40
|
+
} from './cache-plugin.js';
|
|
41
|
+
|
|
42
|
+
describe('Cache Plugin', () => {
|
|
43
|
+
const mockConfig: CachePluginConfig = {
|
|
44
|
+
url: 'redis://localhost:6379',
|
|
45
|
+
keyPrefix: 'test:',
|
|
46
|
+
defaultTtl: 3600,
|
|
47
|
+
healthCheck: false, // Disable for unit tests
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const mockContext = {
|
|
51
|
+
config: { productName: 'Test', port: 3000 },
|
|
52
|
+
app: {} as any,
|
|
53
|
+
router: {} as any,
|
|
54
|
+
logger: {
|
|
55
|
+
debug: vi.fn(),
|
|
56
|
+
info: vi.fn(),
|
|
57
|
+
warn: vi.fn(),
|
|
58
|
+
error: vi.fn(),
|
|
59
|
+
},
|
|
60
|
+
registerHealthCheck: vi.fn(),
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
beforeEach(() => {
|
|
64
|
+
vi.clearAllMocks();
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
afterEach(async () => {
|
|
68
|
+
// Clean up any registered instances
|
|
69
|
+
if (hasCache('test')) {
|
|
70
|
+
const cache = getCache('test');
|
|
71
|
+
await cache.close();
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
describe('createCachePlugin', () => {
|
|
76
|
+
it('should create a plugin with correct name', () => {
|
|
77
|
+
const plugin = createCachePlugin(mockConfig, 'test');
|
|
78
|
+
expect(plugin.name).toBe('cache:test');
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('should use "default" as instance name when not specified', () => {
|
|
82
|
+
const plugin = createCachePlugin(mockConfig);
|
|
83
|
+
expect(plugin.name).toBe('cache:default');
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('should have low order number (initialize early)', () => {
|
|
87
|
+
const plugin = createCachePlugin(mockConfig);
|
|
88
|
+
expect(plugin.order).toBeLessThan(10);
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
describe('onInit', () => {
|
|
93
|
+
it('should register the cache instance', async () => {
|
|
94
|
+
const plugin = createCachePlugin(mockConfig, 'test');
|
|
95
|
+
await plugin.onInit?.(mockContext as any);
|
|
96
|
+
|
|
97
|
+
expect(hasCache('test')).toBe(true);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('should log debug message on successful connection', async () => {
|
|
101
|
+
const plugin = createCachePlugin(mockConfig, 'test');
|
|
102
|
+
await plugin.onInit?.(mockContext as any);
|
|
103
|
+
|
|
104
|
+
expect(mockContext.logger.debug).toHaveBeenCalledWith(
|
|
105
|
+
expect.stringContaining('connected')
|
|
106
|
+
);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('should register health check when enabled', async () => {
|
|
110
|
+
const configWithHealth = { ...mockConfig, healthCheck: true };
|
|
111
|
+
const plugin = createCachePlugin(configWithHealth, 'test');
|
|
112
|
+
await plugin.onInit?.(mockContext as any);
|
|
113
|
+
|
|
114
|
+
expect(mockContext.registerHealthCheck).toHaveBeenCalledWith(
|
|
115
|
+
expect.objectContaining({
|
|
116
|
+
name: 'redis',
|
|
117
|
+
type: 'custom',
|
|
118
|
+
})
|
|
119
|
+
);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it('should use custom health check name when provided', async () => {
|
|
123
|
+
const configWithCustomName = {
|
|
124
|
+
...mockConfig,
|
|
125
|
+
healthCheck: true,
|
|
126
|
+
healthCheckName: 'custom-cache',
|
|
127
|
+
};
|
|
128
|
+
const plugin = createCachePlugin(configWithCustomName, 'test');
|
|
129
|
+
await plugin.onInit?.(mockContext as any);
|
|
130
|
+
|
|
131
|
+
expect(mockContext.registerHealthCheck).toHaveBeenCalledWith(
|
|
132
|
+
expect.objectContaining({
|
|
133
|
+
name: 'custom-cache',
|
|
134
|
+
})
|
|
135
|
+
);
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
describe('getCache', () => {
|
|
140
|
+
it('should return registered instance', async () => {
|
|
141
|
+
const plugin = createCachePlugin(mockConfig, 'test');
|
|
142
|
+
await plugin.onInit?.(mockContext as any);
|
|
143
|
+
|
|
144
|
+
const cache = getCache('test');
|
|
145
|
+
expect(cache).toBeDefined();
|
|
146
|
+
expect(cache.get).toBeDefined();
|
|
147
|
+
expect(cache.set).toBeDefined();
|
|
148
|
+
expect(cache.delete).toBeDefined();
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it('should throw error for unregistered instance', () => {
|
|
152
|
+
expect(() => getCache('nonexistent')).toThrow(
|
|
153
|
+
'Cache instance "nonexistent" not found'
|
|
154
|
+
);
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
describe('hasCache', () => {
|
|
159
|
+
it('should return false for unregistered instance', () => {
|
|
160
|
+
expect(hasCache('nonexistent')).toBe(false);
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it('should return true for registered instance', async () => {
|
|
164
|
+
const plugin = createCachePlugin(mockConfig, 'test');
|
|
165
|
+
await plugin.onInit?.(mockContext as any);
|
|
166
|
+
|
|
167
|
+
expect(hasCache('test')).toBe(true);
|
|
168
|
+
});
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
describe('CacheInstance', () => {
|
|
172
|
+
it('should get value and parse JSON', async () => {
|
|
173
|
+
const plugin = createCachePlugin(mockConfig, 'test');
|
|
174
|
+
await plugin.onInit?.(mockContext as any);
|
|
175
|
+
|
|
176
|
+
const cache = getCache('test');
|
|
177
|
+
// Mock will return null by default
|
|
178
|
+
const result = await cache.get('key');
|
|
179
|
+
expect(result).toBeNull();
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it('should set value with JSON stringification', async () => {
|
|
183
|
+
const plugin = createCachePlugin(mockConfig, 'test');
|
|
184
|
+
await plugin.onInit?.(mockContext as any);
|
|
185
|
+
|
|
186
|
+
const cache = getCache('test');
|
|
187
|
+
await cache.set('key', { foo: 'bar' }, 3600);
|
|
188
|
+
// Just verify it doesn't throw
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
it('should return cache stats', async () => {
|
|
192
|
+
const plugin = createCachePlugin(mockConfig, 'test');
|
|
193
|
+
await plugin.onInit?.(mockContext as any);
|
|
194
|
+
|
|
195
|
+
const cache = getCache('test');
|
|
196
|
+
const stats = await cache.getStats();
|
|
197
|
+
expect(stats).toHaveProperty('connected');
|
|
198
|
+
expect(stats).toHaveProperty('keyCount');
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
it('should check if key exists', async () => {
|
|
202
|
+
const plugin = createCachePlugin(mockConfig, 'test');
|
|
203
|
+
await plugin.onInit?.(mockContext as any);
|
|
204
|
+
|
|
205
|
+
const cache = getCache('test');
|
|
206
|
+
const exists = await cache.exists('key');
|
|
207
|
+
expect(typeof exists).toBe('boolean');
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
it('should get TTL for a key', async () => {
|
|
211
|
+
const plugin = createCachePlugin(mockConfig, 'test');
|
|
212
|
+
await plugin.onInit?.(mockContext as any);
|
|
213
|
+
|
|
214
|
+
const cache = getCache('test');
|
|
215
|
+
const ttl = await cache.ttl('key');
|
|
216
|
+
expect(typeof ttl).toBe('number');
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
it('should increment a value', async () => {
|
|
220
|
+
const plugin = createCachePlugin(mockConfig, 'test');
|
|
221
|
+
await plugin.onInit?.(mockContext as any);
|
|
222
|
+
|
|
223
|
+
const cache = getCache('test');
|
|
224
|
+
const value = await cache.incr('counter');
|
|
225
|
+
expect(typeof value).toBe('number');
|
|
226
|
+
});
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
describe('onShutdown', () => {
|
|
230
|
+
it('should close client and unregister instance', async () => {
|
|
231
|
+
const plugin = createCachePlugin(mockConfig, 'test');
|
|
232
|
+
await plugin.onInit?.(mockContext as any);
|
|
233
|
+
|
|
234
|
+
expect(hasCache('test')).toBe(true);
|
|
235
|
+
|
|
236
|
+
await plugin.onShutdown?.();
|
|
237
|
+
|
|
238
|
+
expect(hasCache('test')).toBe(false);
|
|
239
|
+
});
|
|
240
|
+
});
|
|
241
|
+
});
|