@elysiumjs/mnemosyne 0.1.2 → 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/.turbo/turbo-build.log +8 -0
- package/CHANGELOG.md +34 -0
- package/README.md +19 -16
- package/dist/cache.d.ts +130 -0
- package/dist/database.d.ts +66 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +2 -0
- package/dist/interfaces.d.ts +350 -0
- package/dist/keyv-redis.d.ts +88 -0
- package/dist/mnemosyne.module.d.ts +44 -0
- package/dist/model.d.ts +68 -0
- package/dist/repository.d.ts +96 -0
- package/dist/tenancy.d.ts +179 -0
- package/package.json +8 -9
- package/src/cache.ts +21 -20
- package/src/database.ts +13 -50
- package/src/index.ts +2 -12
- package/src/keyv-redis.ts +39 -26
- package/src/mnemosyne.module.ts +83 -0
- package/src/tenancy.ts +12 -29
- package/tests/cache.test.ts +51 -10
- package/tests/database.test.ts +37 -173
- package/tests/mnemosyne.module.test.ts +214 -0
- package/tests/setup.ts +0 -13
- package/bunfig.toml +0 -19
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
// Copyright (c) 2026-present Workbud Technologies Inc. All rights reserved.
|
|
2
|
+
//
|
|
3
|
+
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
// you may not use this file except in compliance with the License.
|
|
5
|
+
// You may obtain a copy of the License at
|
|
6
|
+
//
|
|
7
|
+
// http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
//
|
|
9
|
+
// Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
// See the License for the specific language governing permissions and
|
|
13
|
+
// limitations under the License.
|
|
14
|
+
|
|
15
|
+
import type { ServiceFactoryDefinition } from '@elysiumjs/core';
|
|
16
|
+
|
|
17
|
+
import { Module } from '@elysiumjs/core';
|
|
18
|
+
import { KeyvCacheableMemory } from 'cacheable';
|
|
19
|
+
|
|
20
|
+
import { makeCacheInterface, MEMORY_CACHE_SERVICE, REDIS_CACHE_SERVICE } from './cache';
|
|
21
|
+
import { KeyvRedis } from './keyv-redis';
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Configuration of the {@link MnemosyneModule}.
|
|
25
|
+
* @author Axel Nana <axel.nana@workbud.com>
|
|
26
|
+
*/
|
|
27
|
+
export type MnemosyneModuleConfig = {
|
|
28
|
+
cache: {
|
|
29
|
+
/** The Redis-backed cache. Set to `false` to not provide `Cache.redis`. */
|
|
30
|
+
redis?:
|
|
31
|
+
| {
|
|
32
|
+
/** The name of the Redis connection to use. */
|
|
33
|
+
connection: string;
|
|
34
|
+
}
|
|
35
|
+
| false;
|
|
36
|
+
|
|
37
|
+
/** The memory cache. Set to `false` to not provide `Cache.memory`. */
|
|
38
|
+
memory?:
|
|
39
|
+
| {
|
|
40
|
+
/** The default TTL of entries, in milliseconds. */
|
|
41
|
+
ttl: number;
|
|
42
|
+
|
|
43
|
+
/** The maximum number of entries kept. */
|
|
44
|
+
lruSize: number;
|
|
45
|
+
}
|
|
46
|
+
| false;
|
|
47
|
+
};
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Provides the caches as the global services `mnemosyne.cache.redis` and
|
|
52
|
+
* `mnemosyne.cache.memory`. Tenancy config and strategy, and database connections, are
|
|
53
|
+
* registered by the database driver modules.
|
|
54
|
+
* @author Axel Nana <axel.nana@workbud.com>
|
|
55
|
+
*/
|
|
56
|
+
@Module.register<MnemosyneModuleConfig>({
|
|
57
|
+
services: (config) => {
|
|
58
|
+
const services: ServiceFactoryDefinition[] = [];
|
|
59
|
+
const { redis, memory } = config.cache;
|
|
60
|
+
|
|
61
|
+
if (redis) {
|
|
62
|
+
services.push({
|
|
63
|
+
name: REDIS_CACHE_SERVICE,
|
|
64
|
+
global: true,
|
|
65
|
+
factory: () => makeCacheInterface(new KeyvRedis({ connection: redis.connection }))
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (memory) {
|
|
70
|
+
services.push({
|
|
71
|
+
name: MEMORY_CACHE_SERVICE,
|
|
72
|
+
global: true,
|
|
73
|
+
factory: () =>
|
|
74
|
+
makeCacheInterface(new KeyvCacheableMemory({ ttl: memory.ttl, lruSize: memory.lruSize }))
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return services;
|
|
79
|
+
}
|
|
80
|
+
})
|
|
81
|
+
export class MnemosyneModule extends Module.configurable<MnemosyneModuleConfig>({
|
|
82
|
+
defaults: { cache: { redis: { connection: 'cache' }, memory: { ttl: 60000, lruSize: 5000 } } }
|
|
83
|
+
}) {}
|
package/src/tenancy.ts
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
import type { Context } from '@elysiumjs/core';
|
|
16
16
|
import type { TenancyStrategy } from './interfaces';
|
|
17
17
|
|
|
18
|
-
import { Application, Middleware } from '@elysiumjs/core';
|
|
18
|
+
import { Application, Middleware, Service } from '@elysiumjs/core';
|
|
19
19
|
|
|
20
20
|
// ============================================================================
|
|
21
21
|
// Types
|
|
@@ -95,53 +95,36 @@ export interface ModelTenancyConfig {
|
|
|
95
95
|
}
|
|
96
96
|
|
|
97
97
|
// ============================================================================
|
|
98
|
-
//
|
|
98
|
+
// Configuration
|
|
99
99
|
// ============================================================================
|
|
100
100
|
|
|
101
|
-
|
|
101
|
+
/** Name of the global service holding the {@link TenancyConfig}, registered by driver modules. */
|
|
102
|
+
export const TENANCY_CONFIG_SERVICE = 'mnemosyne.tenancy.config';
|
|
102
103
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
// ============================================================================
|
|
104
|
+
/** Name of the global service holding the {@link TenancyStrategy}, registered by driver modules. */
|
|
105
|
+
export const TENANCY_STRATEGY_SERVICE = 'mnemosyne.tenancy.strategy';
|
|
106
106
|
|
|
107
107
|
/**
|
|
108
108
|
* Gets the current global tenancy configuration.
|
|
109
109
|
*
|
|
110
|
-
* Reads
|
|
111
|
-
* configuration if
|
|
110
|
+
* Reads the global service `mnemosyne.tenancy.config`. Returns a default
|
|
111
|
+
* configuration if none is registered.
|
|
112
112
|
*
|
|
113
113
|
* @author Axel Nana <axel.nana@workbud.com>
|
|
114
114
|
* @returns The current tenancy configuration.
|
|
115
115
|
*/
|
|
116
116
|
export const getTenancyConfig = (): TenancyConfig => {
|
|
117
|
-
|
|
118
|
-
if (!app) {
|
|
119
|
-
return { mode: 'schema' };
|
|
120
|
-
}
|
|
121
|
-
const config = app.getConfig<{ tenancy?: TenancyConfig }>('elysium:mnemosyne' as any);
|
|
122
|
-
return config?.tenancy ?? { mode: 'schema' };
|
|
123
|
-
};
|
|
124
|
-
|
|
125
|
-
// ============================================================================
|
|
126
|
-
// Strategy Registry
|
|
127
|
-
// ============================================================================
|
|
128
|
-
|
|
129
|
-
/**
|
|
130
|
-
* Registers the tenancy strategy to use for table resolution and isolation.
|
|
131
|
-
* @author Axel Nana <axel.nana@workbud.com>
|
|
132
|
-
* @param strategy The tenancy strategy to register.
|
|
133
|
-
*/
|
|
134
|
-
export const registerTenancyStrategy = (strategy: TenancyStrategy<unknown, unknown>): void => {
|
|
135
|
-
_strategy = strategy;
|
|
117
|
+
return Service.get<TenancyConfig>(TENANCY_CONFIG_SERVICE) ?? { mode: 'schema' };
|
|
136
118
|
};
|
|
137
119
|
|
|
138
120
|
/**
|
|
139
|
-
* Gets the
|
|
121
|
+
* Gets the tenancy strategy registered by the driver module as the global service
|
|
122
|
+
* `mnemosyne.tenancy.strategy`.
|
|
140
123
|
* @author Axel Nana <axel.nana@workbud.com>
|
|
141
124
|
* @returns The current tenancy strategy, or `null` if none is registered.
|
|
142
125
|
*/
|
|
143
126
|
export const getTenancyStrategy = (): TenancyStrategy<unknown, unknown> | null => {
|
|
144
|
-
return
|
|
127
|
+
return Service.get<TenancyStrategy<unknown, unknown>>(TENANCY_STRATEGY_SERVICE);
|
|
145
128
|
};
|
|
146
129
|
|
|
147
130
|
// ============================================================================
|
package/tests/cache.test.ts
CHANGED
|
@@ -14,21 +14,63 @@
|
|
|
14
14
|
|
|
15
15
|
import type { Mock } from 'bun:test';
|
|
16
16
|
|
|
17
|
+
import { Application, RedisModule } from '@elysiumjs/core';
|
|
17
18
|
import { afterAll, beforeAll, beforeEach, describe, expect, it, mock, spyOn } from 'bun:test';
|
|
18
19
|
import { createCache } from 'cache-manager';
|
|
19
20
|
import * as KV from 'keyv';
|
|
20
21
|
|
|
21
|
-
import {
|
|
22
|
-
|
|
23
|
-
import { Cache } from '../src/cache';
|
|
22
|
+
import { Cache, MnemosyneModule } from '../src';
|
|
24
23
|
|
|
25
24
|
// Mock dependencies
|
|
26
25
|
mock.module('cache-manager', () => ({
|
|
27
26
|
createCache: mock(createCache)
|
|
28
27
|
}));
|
|
29
28
|
|
|
30
|
-
|
|
31
|
-
|
|
29
|
+
// A minimal in-memory stand-in for Bun.RedisClient
|
|
30
|
+
const data = new Map<string, string>();
|
|
31
|
+
const originalRedisClient = Bun.RedisClient;
|
|
32
|
+
const RedisClientFake = mock(function () {
|
|
33
|
+
return {
|
|
34
|
+
close: mock(),
|
|
35
|
+
get: async (k: string) => data.get(k) ?? null,
|
|
36
|
+
set: async (k: string, v: string) => {
|
|
37
|
+
data.set(k, v);
|
|
38
|
+
return 'OK';
|
|
39
|
+
},
|
|
40
|
+
del: async (k: string) => Number(data.delete(k)),
|
|
41
|
+
exists: async (k: string) => data.has(k),
|
|
42
|
+
mget: async (...keys: string[]) => keys.map((k) => data.get(k) ?? null),
|
|
43
|
+
send: async (cmd: string, args: string[]) => {
|
|
44
|
+
if (cmd === 'UNLINK') return Number(data.delete(args[0]));
|
|
45
|
+
if (cmd === 'SCAN') return ['0', [...data.keys()]];
|
|
46
|
+
if (cmd === 'FLUSHDB') return data.clear();
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
}) as unknown as typeof Bun.RedisClient;
|
|
51
|
+
|
|
52
|
+
let app: Awaited<ReturnType<typeof Application.testing>>;
|
|
53
|
+
|
|
54
|
+
beforeAll(async () => {
|
|
55
|
+
Bun.RedisClient = RedisClientFake;
|
|
56
|
+
app = await Application.testing({
|
|
57
|
+
modules: [
|
|
58
|
+
RedisModule.with({
|
|
59
|
+
default: 'cache',
|
|
60
|
+
connections: { cache: { url: 'redis://cache:6379' } }
|
|
61
|
+
}),
|
|
62
|
+
MnemosyneModule
|
|
63
|
+
]
|
|
64
|
+
});
|
|
65
|
+
await app.get('mnemosyne.cache.redis');
|
|
66
|
+
await app.get('mnemosyne.cache.memory');
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
afterAll(async () => {
|
|
70
|
+
// Some tests spy on Keyv without restoring it
|
|
71
|
+
mock.restore();
|
|
72
|
+
await app.shutdown();
|
|
73
|
+
Bun.RedisClient = originalRedisClient;
|
|
32
74
|
});
|
|
33
75
|
|
|
34
76
|
// Test the weak cache
|
|
@@ -82,9 +124,8 @@ describe('Cache.weak', () => {
|
|
|
82
124
|
|
|
83
125
|
// Test the Redis cache
|
|
84
126
|
describe('Cache.redis', () => {
|
|
85
|
-
beforeAll(() => {
|
|
86
|
-
Cache.redis.clear();
|
|
87
|
-
Event.emit('elysium:app:launched', null, null);
|
|
127
|
+
beforeAll(async () => {
|
|
128
|
+
await Cache.redis.clear();
|
|
88
129
|
});
|
|
89
130
|
|
|
90
131
|
// Reset mocks before each test
|
|
@@ -109,7 +150,7 @@ describe('Cache.redis', () => {
|
|
|
109
150
|
expect(getSpy).toHaveBeenCalledWith('test-key');
|
|
110
151
|
|
|
111
152
|
// Check if the result is correct
|
|
112
|
-
expect(result).
|
|
153
|
+
expect(result).toBeUndefined();
|
|
113
154
|
});
|
|
114
155
|
|
|
115
156
|
it('should call set method with the correct parameters', async () => {
|
|
@@ -443,7 +484,7 @@ describe('Cache namespacing and tagging', () => {
|
|
|
443
484
|
Cache.memory.tags('tag1', 'tag2', 'tag3');
|
|
444
485
|
|
|
445
486
|
// Check if Keyv was instantiated with the correct namespace
|
|
446
|
-
expect(keyvSpy).
|
|
487
|
+
expect(keyvSpy).toHaveBeenCalledWith(
|
|
447
488
|
expect.objectContaining({
|
|
448
489
|
namespace: 'cache__tag1__tag2__tag3'
|
|
449
490
|
})
|
package/tests/database.test.ts
CHANGED
|
@@ -12,16 +12,15 @@
|
|
|
12
12
|
// See the License for the specific language governing permissions and
|
|
13
13
|
// limitations under the License.
|
|
14
14
|
|
|
15
|
-
import {
|
|
16
|
-
|
|
17
|
-
import { Application, Service } from '@elysiumjs/core';
|
|
15
|
+
import { Application, Service, ServiceNotFoundError } from '@elysiumjs/core';
|
|
16
|
+
import { afterEach, describe, expect, it, jest, mock, spyOn } from 'bun:test';
|
|
18
17
|
|
|
19
18
|
import { AbstractDatabase } from '../src/database';
|
|
20
19
|
|
|
21
20
|
const mockDriver = {
|
|
22
21
|
createConnection: mock((config: any) => ({ mockConnection: true, config })),
|
|
23
22
|
withTransaction: mock((conn: any, cb: any) => cb(conn)),
|
|
24
|
-
getRawClient: mock((
|
|
23
|
+
getRawClient: mock((_conn: any) => ({ rawClient: true }))
|
|
25
24
|
};
|
|
26
25
|
|
|
27
26
|
class TestDatabase extends AbstractDatabase<any, any> {
|
|
@@ -29,220 +28,85 @@ class TestDatabase extends AbstractDatabase<any, any> {
|
|
|
29
28
|
}
|
|
30
29
|
|
|
31
30
|
describe('AbstractDatabase', () => {
|
|
32
|
-
// Mock console.error and process.exit to prevent actual console output and process exit
|
|
33
|
-
const originalConsoleError = console.error;
|
|
34
|
-
const originalProcessExit = process.exit;
|
|
35
|
-
|
|
36
|
-
beforeEach(() => {
|
|
37
|
-
console.error = mock();
|
|
38
|
-
process.exit = mock() as any;
|
|
39
|
-
});
|
|
40
|
-
|
|
41
31
|
afterEach(() => {
|
|
42
|
-
|
|
43
|
-
process.exit = originalProcessExit;
|
|
32
|
+
jest.restoreAllMocks();
|
|
44
33
|
jest.clearAllMocks();
|
|
45
34
|
});
|
|
46
35
|
|
|
47
|
-
describe('registerConnection', () => {
|
|
48
|
-
it('should register a new connection', () => {
|
|
49
|
-
const db = new TestDatabase();
|
|
50
|
-
|
|
51
|
-
// Mock Service.exists to return false (connection doesn't exist)
|
|
52
|
-
const existsSpy = spyOn(Service, 'exists').mockReturnValueOnce(false);
|
|
53
|
-
const instanceSpy = spyOn(Service, 'instance');
|
|
54
|
-
|
|
55
|
-
// Call the function
|
|
56
|
-
const result = db.registerConnection('test', { connection: 'sqlite:test.db' });
|
|
57
|
-
|
|
58
|
-
// Check if Service.exists was called with the correct connection name
|
|
59
|
-
expect(existsSpy).toHaveBeenCalledWith('db.connection.test');
|
|
60
|
-
|
|
61
|
-
// Check if the driver's createConnection was called with the config
|
|
62
|
-
expect(mockDriver.createConnection).toHaveBeenCalledWith({ connection: 'sqlite:test.db' });
|
|
63
|
-
|
|
64
|
-
// Check if Service.instance was called with the correct parameters
|
|
65
|
-
expect(instanceSpy).toHaveBeenCalledWith('db.connection.test', {
|
|
66
|
-
mockConnection: true,
|
|
67
|
-
config: { connection: 'sqlite:test.db' }
|
|
68
|
-
});
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
it('should throw an error if the connection already exists', () => {
|
|
72
|
-
const db = new TestDatabase();
|
|
73
|
-
|
|
74
|
-
// Mock Service.exists to return true (connection exists)
|
|
75
|
-
const existsSpy = spyOn(Service, 'exists').mockReturnValueOnce(true);
|
|
76
|
-
|
|
77
|
-
// Call the function
|
|
78
|
-
db.registerConnection('test', { connection: 'sqlite:test.db' });
|
|
79
|
-
|
|
80
|
-
// Check if console.error and process.exit were called
|
|
81
|
-
expect(console.error).toHaveBeenCalledWith(
|
|
82
|
-
expect.stringContaining('A connection with the name test has already been registered')
|
|
83
|
-
);
|
|
84
|
-
expect(process.exit).toHaveBeenCalledWith(1);
|
|
85
|
-
});
|
|
86
|
-
});
|
|
87
|
-
|
|
88
36
|
describe('getConnection', () => {
|
|
89
37
|
it('should retrieve an existing connection', () => {
|
|
90
|
-
const db = new TestDatabase();
|
|
91
|
-
|
|
92
|
-
// Mock Service.exists to return true (connection exists)
|
|
93
|
-
const existsSpy = spyOn(Service, 'exists').mockReturnValueOnce(true);
|
|
94
|
-
|
|
95
|
-
// Mock Service.get to return a mock connection
|
|
96
38
|
const mockConnection = { mockConnection: true };
|
|
97
39
|
const getSpy = spyOn(Service, 'get').mockReturnValue(mockConnection);
|
|
98
40
|
|
|
99
|
-
|
|
100
|
-
const result = db.getConnection('test');
|
|
101
|
-
|
|
102
|
-
// Check if Service.exists was called with the correct connection name
|
|
103
|
-
expect(existsSpy).toHaveBeenCalledWith('db.connection.test');
|
|
41
|
+
const result = new TestDatabase().getConnection('test');
|
|
104
42
|
|
|
105
|
-
// Check if Service.get was called with the correct connection name
|
|
106
43
|
expect(getSpy).toHaveBeenCalledWith('db.connection.test');
|
|
107
|
-
|
|
108
|
-
// Check if the result is correct
|
|
109
|
-
expect(result as any).toEqual(mockConnection);
|
|
44
|
+
expect(result).toBe(mockConnection);
|
|
110
45
|
});
|
|
111
46
|
|
|
112
|
-
it('should throw
|
|
113
|
-
|
|
47
|
+
it('should throw ServiceNotFoundError if the connection does not exist', () => {
|
|
48
|
+
spyOn(Service, 'get').mockReturnValue(null);
|
|
114
49
|
|
|
115
|
-
|
|
116
|
-
|
|
50
|
+
expect(() => new TestDatabase().getConnection('test')).toThrow(ServiceNotFoundError);
|
|
51
|
+
});
|
|
117
52
|
|
|
118
|
-
|
|
119
|
-
|
|
53
|
+
it('should return the transaction handle for the default connection', () => {
|
|
54
|
+
const tx = { tx: true };
|
|
55
|
+
const originalDescriptor = Object.getOwnPropertyDescriptor(Application, 'context');
|
|
56
|
+
Object.defineProperty(Application, 'context', {
|
|
57
|
+
get: () => ({ getStore: () => new Map([['db:tx', tx]]) }),
|
|
58
|
+
configurable: true
|
|
59
|
+
});
|
|
120
60
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
61
|
+
try {
|
|
62
|
+
expect(new TestDatabase().getConnection()).toBe(tx);
|
|
63
|
+
} finally {
|
|
64
|
+
if (originalDescriptor) Object.defineProperty(Application, 'context', originalDescriptor);
|
|
65
|
+
}
|
|
126
66
|
});
|
|
127
67
|
});
|
|
128
68
|
|
|
129
69
|
describe('connectionExists', () => {
|
|
130
|
-
it('should
|
|
131
|
-
const
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
const existsSpy = spyOn(Service, 'exists').mockReturnValueOnce(true);
|
|
135
|
-
|
|
136
|
-
// Call the function
|
|
137
|
-
const result = db.connectionExists('test');
|
|
138
|
-
|
|
139
|
-
// Check if Service.exists was called with the correct connection name
|
|
140
|
-
expect(existsSpy).toHaveBeenCalledWith('db.connection.test');
|
|
141
|
-
|
|
142
|
-
// Check if the result is correct
|
|
143
|
-
expect(result).toBe(true);
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
it('should return false if the connection does not exist', () => {
|
|
70
|
+
it('should delegate to Service.exists', () => {
|
|
71
|
+
const existsSpy = spyOn(Service, 'exists')
|
|
72
|
+
.mockReturnValueOnce(true)
|
|
73
|
+
.mockReturnValueOnce(false);
|
|
147
74
|
const db = new TestDatabase();
|
|
148
75
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
// Call the function
|
|
153
|
-
const result = db.connectionExists('test');
|
|
154
|
-
|
|
155
|
-
// Check if Service.exists was called with the correct connection name
|
|
76
|
+
expect(db.connectionExists('test')).toBe(true);
|
|
77
|
+
expect(db.connectionExists('test')).toBe(false);
|
|
156
78
|
expect(existsSpy).toHaveBeenCalledWith('db.connection.test');
|
|
157
|
-
|
|
158
|
-
// Check if the result is correct
|
|
159
|
-
expect(result).toBe(false);
|
|
160
79
|
});
|
|
161
80
|
});
|
|
162
81
|
|
|
163
82
|
describe('getDefaultConnection', () => {
|
|
164
83
|
it('should retrieve the default connection', () => {
|
|
165
|
-
const db = new TestDatabase();
|
|
166
|
-
|
|
167
|
-
// Save original descriptor and mock Application.context
|
|
168
|
-
const originalDescriptor = Object.getOwnPropertyDescriptor(Application, 'context');
|
|
169
|
-
Object.defineProperty(Application, 'context', {
|
|
170
|
-
get: () => ({ getStore: () => null }),
|
|
171
|
-
configurable: true,
|
|
172
|
-
});
|
|
173
|
-
|
|
174
|
-
// Mock Service.exists to return true and Service.get to return a mock connection
|
|
175
|
-
const mockConnection = { mockConnection: true };
|
|
176
|
-
spyOn(Service, 'exists').mockReturnValueOnce(true);
|
|
177
|
-
spyOn(Service, 'get').mockReturnValue(mockConnection);
|
|
178
|
-
|
|
179
|
-
// Call the function
|
|
180
|
-
const result = db.getDefaultConnection();
|
|
181
|
-
|
|
182
|
-
// Check if the result is correct
|
|
183
|
-
expect(result as any).toEqual(mockConnection);
|
|
184
|
-
|
|
185
|
-
// Restore original descriptor
|
|
186
|
-
if (originalDescriptor) {
|
|
187
|
-
Object.defineProperty(Application, 'context', originalDescriptor);
|
|
188
|
-
}
|
|
189
|
-
});
|
|
190
|
-
});
|
|
191
|
-
|
|
192
|
-
describe('setDefaultConnection', () => {
|
|
193
|
-
it('should set the default connection', () => {
|
|
194
|
-
const db = new TestDatabase();
|
|
195
|
-
|
|
196
|
-
// Save original descriptor and mock Application.context
|
|
197
84
|
const originalDescriptor = Object.getOwnPropertyDescriptor(Application, 'context');
|
|
198
85
|
Object.defineProperty(Application, 'context', {
|
|
199
86
|
get: () => ({ getStore: () => null }),
|
|
200
|
-
configurable: true
|
|
87
|
+
configurable: true
|
|
201
88
|
});
|
|
202
89
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
// Mock getConnection to return a mock connection
|
|
207
|
-
const mockConnection = { mockConnection: true };
|
|
208
|
-
spyOn(Service, 'exists').mockReturnValue(true);
|
|
209
|
-
spyOn(Service, 'get').mockReturnValue(mockConnection);
|
|
210
|
-
|
|
211
|
-
// Call the function
|
|
212
|
-
const result = db.setDefaultConnection('test');
|
|
213
|
-
|
|
214
|
-
// Check if Service.remove was called with the correct connection name
|
|
215
|
-
expect(removeSpy).toHaveBeenCalledWith('db.connection.default');
|
|
216
|
-
|
|
217
|
-
// Check if Service.instance was called with the correct parameters
|
|
218
|
-
expect(instanceSpy).toHaveBeenCalledWith('db.connection.default', mockConnection);
|
|
90
|
+
try {
|
|
91
|
+
const mockConnection = { mockConnection: true };
|
|
92
|
+
const getSpy = spyOn(Service, 'get').mockReturnValue(mockConnection);
|
|
219
93
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
if (originalDescriptor) {
|
|
225
|
-
Object.defineProperty(Application, 'context', originalDescriptor);
|
|
94
|
+
expect(new TestDatabase().getDefaultConnection()).toBe(mockConnection);
|
|
95
|
+
expect(getSpy).toHaveBeenCalledWith('db.connection.default');
|
|
96
|
+
} finally {
|
|
97
|
+
if (originalDescriptor) Object.defineProperty(Application, 'context', originalDescriptor);
|
|
226
98
|
}
|
|
227
99
|
});
|
|
228
100
|
});
|
|
229
101
|
|
|
230
102
|
describe('getRawClient', () => {
|
|
231
103
|
it('should return the raw client from the driver', () => {
|
|
232
|
-
const db = new TestDatabase();
|
|
233
|
-
|
|
234
|
-
// Mock getConnection to return a mock connection
|
|
235
104
|
const mockConnection = { mockConnection: true };
|
|
236
|
-
spyOn(Service, 'exists').mockReturnValue(true);
|
|
237
105
|
spyOn(Service, 'get').mockReturnValue(mockConnection);
|
|
238
106
|
|
|
239
|
-
|
|
240
|
-
const result = db.getRawClient('test');
|
|
107
|
+
const result = new TestDatabase().getRawClient('test');
|
|
241
108
|
|
|
242
|
-
// Check if the driver's getRawClient was called with the connection
|
|
243
109
|
expect(mockDriver.getRawClient).toHaveBeenCalledWith(mockConnection);
|
|
244
|
-
|
|
245
|
-
// Check if the result is correct
|
|
246
110
|
expect(result).toEqual({ rawClient: true });
|
|
247
111
|
});
|
|
248
112
|
});
|