@openora/core 0.4.1-canary.37 → 0.4.1-canary.39
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/dist/.tsbuildinfo +1 -1
- package/dist/admin-console/__tests__/backoffice.router.test.js +67 -51
- package/dist/admin-console/__tests__/backoffice.router.test.js.map +1 -1
- package/dist/admin-console/__tests__/backoffice.service.test.js +1 -13
- package/dist/admin-console/__tests__/backoffice.service.test.js.map +1 -1
- package/dist/analytics/__tests__/analytics.contract.test.js +12 -0
- package/dist/analytics/__tests__/analytics.contract.test.js.map +1 -1
- package/dist/analytics/__tests__/financial-analytics.service.test.js +3 -3
- package/dist/analytics/__tests__/financial-analytics.service.test.js.map +1 -1
- package/dist/analytics/service/financial-analytics.service.d.ts.map +1 -1
- package/dist/analytics/service/financial-analytics.service.js +14 -17
- package/dist/analytics/service/financial-analytics.service.js.map +1 -1
- package/dist/analytics/service/funnel-analytics.service.d.ts.map +1 -1
- package/dist/analytics/service/funnel-analytics.service.js +8 -11
- package/dist/analytics/service/funnel-analytics.service.js.map +1 -1
- package/dist/audit/__tests__/audit.service.test.js +128 -251
- package/dist/audit/__tests__/audit.service.test.js.map +1 -1
- package/dist/casino/gaming/__tests__/gaming.service.test.js +83 -89
- package/dist/casino/gaming/__tests__/gaming.service.test.js.map +1 -1
- package/dist/casino/lobby/__tests__/lobby.cache.test.js +42 -33
- package/dist/casino/lobby/__tests__/lobby.cache.test.js.map +1 -1
- package/dist/cms/__tests__/cms.reads.test.js +59 -80
- package/dist/cms/__tests__/cms.reads.test.js.map +1 -1
- package/dist/cms/__tests__/cms.router.test.js +62 -20
- package/dist/cms/__tests__/cms.router.test.js.map +1 -1
- package/dist/cms/__tests__/cms.service.test.js +116 -14
- package/dist/cms/__tests__/cms.service.test.js.map +1 -1
- package/dist/common/errors/__tests__/errors.mapper.test.d.ts +2 -0
- package/dist/common/errors/__tests__/errors.mapper.test.d.ts.map +1 -0
- package/dist/common/errors/__tests__/errors.mapper.test.js +59 -0
- package/dist/common/errors/__tests__/errors.mapper.test.js.map +1 -0
- package/dist/compliance/__tests__/assert.test.d.ts +2 -0
- package/dist/compliance/__tests__/assert.test.d.ts.map +1 -0
- package/dist/compliance/__tests__/assert.test.js +34 -0
- package/dist/compliance/__tests__/assert.test.js.map +1 -0
- package/dist/compliance/__tests__/compliance.service.test.js +149 -84
- package/dist/compliance/__tests__/compliance.service.test.js.map +1 -1
- package/dist/compliance/__tests__/kyc.service.test.js +247 -155
- package/dist/compliance/__tests__/kyc.service.test.js.map +1 -1
- package/dist/compliance/__tests__/limits.contract.test.d.ts +2 -0
- package/dist/compliance/__tests__/limits.contract.test.d.ts.map +1 -0
- package/dist/compliance/__tests__/limits.contract.test.js +43 -0
- package/dist/compliance/__tests__/limits.contract.test.js.map +1 -0
- package/dist/compliance/__tests__/rg-monitoring.service.test.js +256 -101
- package/dist/compliance/__tests__/rg-monitoring.service.test.js.map +1 -1
- package/dist/compliance/__tests__/rg.contract.test.js +1 -20
- package/dist/compliance/__tests__/rg.contract.test.js.map +1 -1
- package/dist/compliance/__tests__/rg.router.test.js +113 -65
- package/dist/compliance/__tests__/rg.router.test.js.map +1 -1
- package/dist/compliance/__tests__/rg.service.test.js +277 -235
- package/dist/compliance/__tests__/rg.service.test.js.map +1 -1
- package/dist/compliance/service/rg-monitoring.service.d.ts.map +1 -1
- package/dist/compliance/service/rg-monitoring.service.js +11 -8
- package/dist/compliance/service/rg-monitoring.service.js.map +1 -1
- package/dist/contracts/__tests__/compose-contract.test.d.ts +2 -0
- package/dist/contracts/__tests__/compose-contract.test.d.ts.map +1 -0
- package/dist/contracts/__tests__/compose-contract.test.js +27 -0
- package/dist/contracts/__tests__/compose-contract.test.js.map +1 -0
- package/dist/contracts/adapters/__tests__/token.test.d.ts +2 -0
- package/dist/contracts/adapters/__tests__/token.test.d.ts.map +1 -0
- package/dist/contracts/adapters/__tests__/token.test.js +14 -0
- package/dist/contracts/adapters/__tests__/token.test.js.map +1 -0
- package/dist/contracts/schemas/__tests__/events.test.d.ts +2 -0
- package/dist/contracts/schemas/__tests__/events.test.d.ts.map +1 -0
- package/dist/contracts/schemas/__tests__/events.test.js +51 -0
- package/dist/contracts/schemas/__tests__/events.test.js.map +1 -0
- package/dist/contracts/schemas/__tests__/igaming-config.test.d.ts +2 -0
- package/dist/contracts/schemas/__tests__/igaming-config.test.d.ts.map +1 -0
- package/dist/contracts/schemas/__tests__/igaming-config.test.js +38 -0
- package/dist/contracts/schemas/__tests__/igaming-config.test.js.map +1 -0
- package/dist/contracts/schemas/reporting.d.ts +7 -2
- package/dist/contracts/schemas/reporting.d.ts.map +1 -1
- package/dist/contracts/schemas/reporting.js +2 -1
- package/dist/contracts/schemas/reporting.js.map +1 -1
- package/dist/engagement/chat/__tests__/chat.service.test.js +524 -364
- package/dist/engagement/chat/__tests__/chat.service.test.js.map +1 -1
- package/dist/engagement/notifications/__tests__/notifications.service.test.js +125 -68
- package/dist/engagement/notifications/__tests__/notifications.service.test.js.map +1 -1
- package/dist/iam/__tests__/iam.service.test.js +367 -596
- package/dist/iam/__tests__/iam.service.test.js.map +1 -1
- package/dist/pam/identity/__tests__/admin-user-directory.test.js +144 -68
- package/dist/pam/identity/__tests__/admin-user-directory.test.js.map +1 -1
- package/dist/pam/identity/__tests__/identity.rate-limit.test.js +33 -22
- package/dist/pam/identity/__tests__/identity.rate-limit.test.js.map +1 -1
- package/dist/pam/identity/__tests__/identity.service.test.js +217 -298
- package/dist/pam/identity/__tests__/identity.service.test.js.map +1 -1
- package/dist/pam/identity/__tests__/login-enforcement.service.test.js +97 -32
- package/dist/pam/identity/__tests__/login-enforcement.service.test.js.map +1 -1
- package/dist/pam/identity/__tests__/phone-login.service.test.js +163 -134
- package/dist/pam/identity/__tests__/phone-login.service.test.js.map +1 -1
- package/dist/pam/identity/__tests__/rg-guard.test.d.ts +2 -0
- package/dist/pam/identity/__tests__/rg-guard.test.d.ts.map +1 -0
- package/dist/pam/identity/__tests__/rg-guard.test.js +21 -0
- package/dist/pam/identity/__tests__/rg-guard.test.js.map +1 -0
- package/dist/pam/player-management/__tests__/kyc-status-writer.test.js +73 -26
- package/dist/pam/player-management/__tests__/kyc-status-writer.test.js.map +1 -1
- package/dist/pam/player-management/__tests__/player.router.test.js +63 -46
- package/dist/pam/player-management/__tests__/player.router.test.js.map +1 -1
- package/dist/pam/player-management/__tests__/player.service.test.js +207 -111
- package/dist/pam/player-management/__tests__/player.service.test.js.map +1 -1
- package/dist/pam/profile/__tests__/profile.service.test.js +89 -41
- package/dist/pam/profile/__tests__/profile.service.test.js.map +1 -1
- package/dist/pam/shared/__tests__/shared-mappers.test.d.ts +2 -0
- package/dist/pam/shared/__tests__/shared-mappers.test.d.ts.map +1 -0
- package/dist/pam/shared/__tests__/shared-mappers.test.js +55 -0
- package/dist/pam/shared/__tests__/shared-mappers.test.js.map +1 -0
- package/dist/pam/tag/__tests__/get-active-tag-keys.test.js +72 -30
- package/dist/pam/tag/__tests__/get-active-tag-keys.test.js.map +1 -1
- package/dist/pam/tag/__tests__/tag-evaluation.service.test.js +308 -235
- package/dist/pam/tag/__tests__/tag-evaluation.service.test.js.map +1 -1
- package/dist/pam/tag/__tests__/tag-rule.service.test.js +136 -140
- package/dist/pam/tag/__tests__/tag-rule.service.test.js.map +1 -1
- package/dist/pam/tag/__tests__/tag.router.test.js +76 -25
- package/dist/pam/tag/__tests__/tag.router.test.js.map +1 -1
- package/dist/pam/tag/__tests__/tag.service.test.js +224 -247
- package/dist/pam/tag/__tests__/tag.service.test.js.map +1 -1
- package/dist/server/auth/__tests__/admin-guard.test.js +107 -117
- package/dist/server/auth/__tests__/admin-guard.test.js.map +1 -1
- package/dist/server/auth/__tests__/permission-levels.test.d.ts +2 -0
- package/dist/server/auth/__tests__/permission-levels.test.d.ts.map +1 -0
- package/dist/server/auth/__tests__/permission-levels.test.js +89 -0
- package/dist/server/auth/__tests__/permission-levels.test.js.map +1 -0
- package/dist/server/auth/__tests__/session-resolver.test.js +45 -13
- package/dist/server/auth/__tests__/session-resolver.test.js.map +1 -1
- package/dist/server/db/__tests__/query-helpers.test.js +72 -1
- package/dist/server/db/__tests__/query-helpers.test.js.map +1 -1
- package/dist/server/db/outbox/__tests__/relay.test.js +74 -81
- package/dist/server/db/outbox/__tests__/relay.test.js.map +1 -1
- package/dist/server/kernel/__tests__/bullmq-job-queue.test.js +204 -154
- package/dist/server/kernel/__tests__/bullmq-job-queue.test.js.map +1 -1
- package/dist/server/kernel/__tests__/cache.test.js +26 -13
- package/dist/server/kernel/__tests__/cache.test.js.map +1 -1
- package/dist/server/kernel/__tests__/domain-error.test.d.ts +2 -0
- package/dist/server/kernel/__tests__/domain-error.test.d.ts.map +1 -0
- package/dist/server/kernel/__tests__/domain-error.test.js +35 -0
- package/dist/server/kernel/__tests__/domain-error.test.js.map +1 -0
- package/dist/server/kernel/__tests__/error-reporter.test.d.ts +2 -0
- package/dist/server/kernel/__tests__/error-reporter.test.d.ts.map +1 -0
- package/dist/server/kernel/__tests__/error-reporter.test.js +52 -0
- package/dist/server/kernel/__tests__/error-reporter.test.js.map +1 -0
- package/dist/server/kernel/__tests__/event-bus.test.js +60 -20
- package/dist/server/kernel/__tests__/event-bus.test.js.map +1 -1
- package/dist/server/kernel/__tests__/orpc-error-map.test.d.ts +2 -0
- package/dist/server/kernel/__tests__/orpc-error-map.test.d.ts.map +1 -0
- package/dist/server/kernel/__tests__/orpc-error-map.test.js +54 -0
- package/dist/server/kernel/__tests__/orpc-error-map.test.js.map +1 -0
- package/dist/server/kernel/__tests__/platform-config-loader.test.d.ts +2 -0
- package/dist/server/kernel/__tests__/platform-config-loader.test.d.ts.map +1 -0
- package/dist/server/kernel/__tests__/platform-config-loader.test.js +112 -0
- package/dist/server/kernel/__tests__/platform-config-loader.test.js.map +1 -0
- package/dist/server/kernel/__tests__/rate-limiter.test.js +22 -13
- package/dist/server/kernel/__tests__/rate-limiter.test.js.map +1 -1
- package/dist/server/kernel/__tests__/realtime-transport.test.d.ts.map +1 -0
- package/dist/{testing/fakes → server/kernel}/__tests__/realtime-transport.test.js.map +1 -1
- package/dist/server/kernel/__tests__/redis-cache.test.js +34 -34
- package/dist/server/kernel/__tests__/redis-cache.test.js.map +1 -1
- package/dist/server/kernel/__tests__/redis-rate-limiter.test.js +49 -44
- package/dist/server/kernel/__tests__/redis-rate-limiter.test.js.map +1 -1
- package/dist/server/kernel/__tests__/redis-streams-broker.test.js +141 -262
- package/dist/server/kernel/__tests__/redis-streams-broker.test.js.map +1 -1
- package/dist/server/kernel/__tests__/request-context.test.d.ts +2 -0
- package/dist/server/kernel/__tests__/request-context.test.d.ts.map +1 -0
- package/dist/server/kernel/__tests__/request-context.test.js +64 -0
- package/dist/server/kernel/__tests__/request-context.test.js.map +1 -0
- package/dist/server/kernel/__tests__/router-utils.test.d.ts +2 -0
- package/dist/server/kernel/__tests__/router-utils.test.d.ts.map +1 -0
- package/dist/server/kernel/__tests__/router-utils.test.js +80 -0
- package/dist/server/kernel/__tests__/router-utils.test.js.map +1 -0
- package/dist/server/kernel/bullmq-job-queue.d.ts.map +1 -1
- package/dist/server/kernel/bullmq-job-queue.js +8 -2
- package/dist/server/kernel/bullmq-job-queue.js.map +1 -1
- package/dist/server/runtime/__tests__/create-app.test.js +26 -18
- package/dist/server/runtime/__tests__/create-app.test.js.map +1 -1
- package/dist/testing/index.d.ts +2 -6
- package/dist/testing/index.d.ts.map +1 -1
- package/dist/testing/index.js +2 -10
- package/dist/testing/index.js.map +1 -1
- package/dist/testing/mock.d.ts +28 -16
- package/dist/testing/mock.d.ts.map +1 -1
- package/dist/testing/mock.js +58 -37
- package/dist/testing/mock.js.map +1 -1
- package/dist/testing/real-infra.d.ts +47 -0
- package/dist/testing/real-infra.d.ts.map +1 -0
- package/dist/testing/real-infra.js +122 -0
- package/dist/testing/real-infra.js.map +1 -0
- package/dist/wallet/__tests__/admin-reporting.test.js +201 -90
- package/dist/wallet/__tests__/admin-reporting.test.js.map +1 -1
- package/dist/wallet/__tests__/rail-for.test.d.ts +2 -0
- package/dist/wallet/__tests__/rail-for.test.d.ts.map +1 -0
- package/dist/wallet/__tests__/rail-for.test.js +22 -0
- package/dist/wallet/__tests__/rail-for.test.js.map +1 -0
- package/dist/wallet/__tests__/wallet-auto-rule.router.test.js +82 -63
- package/dist/wallet/__tests__/wallet-auto-rule.router.test.js.map +1 -1
- package/dist/wallet/__tests__/wallet-auto-withdrawal.test.js +414 -437
- package/dist/wallet/__tests__/wallet-auto-withdrawal.test.js.map +1 -1
- package/dist/wallet/__tests__/wallet-commands.service.test.js +116 -155
- package/dist/wallet/__tests__/wallet-commands.service.test.js.map +1 -1
- package/dist/wallet/__tests__/wallet-kyc-gate.test.js +63 -42
- package/dist/wallet/__tests__/wallet-kyc-gate.test.js.map +1 -1
- package/dist/wallet/__tests__/wallet-webhook.router.test.js +129 -51
- package/dist/wallet/__tests__/wallet-webhook.router.test.js.map +1 -1
- package/dist/wallet/__tests__/wallet.rate-limit.test.js +74 -21
- package/dist/wallet/__tests__/wallet.rate-limit.test.js.map +1 -1
- package/dist/wallet/__tests__/wallet.router.test.js +56 -36
- package/dist/wallet/__tests__/wallet.router.test.js.map +1 -1
- package/dist/wallet/__tests__/wallet.service.test.js +623 -887
- package/dist/wallet/__tests__/wallet.service.test.js.map +1 -1
- package/package.json +1 -2
- package/dist/casino/lobby/__tests__/lobby.service.test.d.ts +0 -2
- package/dist/casino/lobby/__tests__/lobby.service.test.d.ts.map +0 -1
- package/dist/casino/lobby/__tests__/lobby.service.test.js +0 -11
- package/dist/casino/lobby/__tests__/lobby.service.test.js.map +0 -1
- package/dist/contracts/schemas/__tests__/identity-schemas.test.d.ts +0 -2
- package/dist/contracts/schemas/__tests__/identity-schemas.test.d.ts.map +0 -1
- package/dist/contracts/schemas/__tests__/identity-schemas.test.js +0 -43
- package/dist/contracts/schemas/__tests__/identity-schemas.test.js.map +0 -1
- package/dist/testing/fakes/__tests__/cache.test.d.ts +0 -2
- package/dist/testing/fakes/__tests__/cache.test.d.ts.map +0 -1
- package/dist/testing/fakes/__tests__/cache.test.js +0 -89
- package/dist/testing/fakes/__tests__/cache.test.js.map +0 -1
- package/dist/testing/fakes/__tests__/job-queue.test.d.ts +0 -2
- package/dist/testing/fakes/__tests__/job-queue.test.d.ts.map +0 -1
- package/dist/testing/fakes/__tests__/job-queue.test.js +0 -135
- package/dist/testing/fakes/__tests__/job-queue.test.js.map +0 -1
- package/dist/testing/fakes/__tests__/rate-limiter.test.d.ts +0 -2
- package/dist/testing/fakes/__tests__/rate-limiter.test.d.ts.map +0 -1
- package/dist/testing/fakes/__tests__/rate-limiter.test.js +0 -83
- package/dist/testing/fakes/__tests__/rate-limiter.test.js.map +0 -1
- package/dist/testing/fakes/__tests__/realtime-authorizer.test.d.ts +0 -2
- package/dist/testing/fakes/__tests__/realtime-authorizer.test.d.ts.map +0 -1
- package/dist/testing/fakes/__tests__/realtime-authorizer.test.js +0 -19
- package/dist/testing/fakes/__tests__/realtime-authorizer.test.js.map +0 -1
- package/dist/testing/fakes/__tests__/realtime-transport.test.d.ts.map +0 -1
- package/dist/testing/fakes/cache.d.ts +0 -14
- package/dist/testing/fakes/cache.d.ts.map +0 -1
- package/dist/testing/fakes/cache.js +0 -56
- package/dist/testing/fakes/cache.js.map +0 -1
- package/dist/testing/fakes/event-bus.d.ts +0 -17
- package/dist/testing/fakes/event-bus.d.ts.map +0 -1
- package/dist/testing/fakes/event-bus.js +0 -30
- package/dist/testing/fakes/event-bus.js.map +0 -1
- package/dist/testing/fakes/job-queue.d.ts +0 -40
- package/dist/testing/fakes/job-queue.d.ts.map +0 -1
- package/dist/testing/fakes/job-queue.js +0 -200
- package/dist/testing/fakes/job-queue.js.map +0 -1
- package/dist/testing/fakes/rate-limiter.d.ts +0 -11
- package/dist/testing/fakes/rate-limiter.d.ts.map +0 -1
- package/dist/testing/fakes/rate-limiter.js +0 -39
- package/dist/testing/fakes/rate-limiter.js.map +0 -1
- package/dist/testing/fakes/realtime-authorizer.d.ts +0 -10
- package/dist/testing/fakes/realtime-authorizer.d.ts.map +0 -1
- package/dist/testing/fakes/realtime-authorizer.js +0 -10
- package/dist/testing/fakes/realtime-authorizer.js.map +0 -1
- package/dist/testing/fakes/realtime-transport.d.ts +0 -2
- package/dist/testing/fakes/realtime-transport.d.ts.map +0 -1
- package/dist/testing/fakes/realtime-transport.js +0 -2
- package/dist/testing/fakes/realtime-transport.js.map +0 -1
- /package/dist/{testing/fakes → server/kernel}/__tests__/realtime-transport.test.d.ts +0 -0
- /package/dist/{testing/fakes → server/kernel}/__tests__/realtime-transport.test.js +0 -0
|
@@ -1,133 +1,123 @@
|
|
|
1
|
-
import { describe, it, expect, vi } from 'vitest';
|
|
2
|
-
import {
|
|
1
|
+
import { describe, it, expect, vi, beforeAll, afterAll, beforeEach } from 'vitest';
|
|
2
|
+
import { randomUUID } from 'node:crypto';
|
|
3
|
+
import { sql } from 'drizzle-orm';
|
|
4
|
+
import { createTestDb } from '@openora/core/testing';
|
|
5
|
+
import { mock, makeEventBus } from '../../../testing/mock.js';
|
|
3
6
|
import { AdminGuard } from '../admin-guard.js';
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
7
|
+
let db;
|
|
8
|
+
const requestContext = (headers) => ({ request: { headers } });
|
|
9
|
+
const ADMIN_HEADERS = { 'x-real-ip': '127.0.0.1', 'user-agent': 'Mozilla/5.0' };
|
|
10
|
+
function makeGuard({ userId, grants, } = {}) {
|
|
11
|
+
const events = makeEventBus();
|
|
12
|
+
const sessions = mock({ resolveUserId: vi.fn(async () => userId) });
|
|
13
|
+
const permissionResolver = grants
|
|
14
|
+
? mock({ getGrants: vi.fn(async () => grants) })
|
|
15
|
+
: undefined;
|
|
16
|
+
const guard = new AdminGuard(db.drizzle, sessions, permissionResolver, events);
|
|
17
|
+
return { guard, events };
|
|
18
|
+
}
|
|
19
|
+
async function seedUser(role) {
|
|
20
|
+
const id = randomUUID();
|
|
21
|
+
await db.drizzle.db.execute(sql `INSERT INTO "user" (id, role) VALUES (${id}, ${role})`);
|
|
22
|
+
return id;
|
|
23
|
+
}
|
|
24
|
+
beforeAll(async () => {
|
|
25
|
+
db = await createTestDb([
|
|
26
|
+
async (databaseUrl) => {
|
|
27
|
+
process.env['DATABASE_URL'] = databaseUrl;
|
|
28
|
+
},
|
|
29
|
+
]);
|
|
30
|
+
await db.drizzle.db.execute(sql `CREATE TABLE "user" (id uuid PRIMARY KEY, role text NOT NULL)`);
|
|
31
|
+
});
|
|
32
|
+
afterAll(async () => {
|
|
33
|
+
await db.drop();
|
|
34
|
+
});
|
|
35
|
+
beforeEach(async () => {
|
|
36
|
+
await db.drizzle.db.execute(sql `TRUNCATE "user"`);
|
|
37
|
+
});
|
|
38
|
+
describe('AdminGuard.assert - authentication (real PG)', () => {
|
|
39
|
+
it('throws UNAUTHORIZED when the context carries no request', async () => {
|
|
40
|
+
const { guard } = makeGuard();
|
|
41
|
+
await expect(guard.assert({})).rejects.toThrow(expect.objectContaining({ code: 'UNAUTHORIZED' }));
|
|
42
|
+
});
|
|
43
|
+
it('throws UNAUTHORIZED when no session resolves', async () => {
|
|
44
|
+
const { guard, events } = makeGuard();
|
|
45
|
+
await expect(guard.assert(requestContext(ADMIN_HEADERS))).rejects.toThrow(expect.objectContaining({ code: 'UNAUTHORIZED' }));
|
|
46
|
+
expect(events.emit).not.toHaveBeenCalled();
|
|
47
|
+
});
|
|
48
|
+
it('throws FORBIDDEN and emits when the session points at a missing user row', async () => {
|
|
49
|
+
const userId = randomUUID();
|
|
50
|
+
const { guard, events } = makeGuard({ userId });
|
|
51
|
+
await expect(guard.assert(requestContext(ADMIN_HEADERS))).rejects.toThrow(expect.objectContaining({ code: 'FORBIDDEN' }));
|
|
52
|
+
expect(events.emit).toHaveBeenCalledWith('identity.user.unauthorized_access', expect.objectContaining({ userId, resource: 'admin', action: 'access', role: undefined }));
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
describe('AdminGuard.assert - static role fallback (real PG)', () => {
|
|
56
|
+
it('returns the caller for an admin with a granted permission', async () => {
|
|
57
|
+
const userId = await seedUser('admin');
|
|
58
|
+
const { guard, events } = makeGuard({ userId });
|
|
59
|
+
const caller = await guard.assert(requestContext(ADMIN_HEADERS), 'admin', 'delete');
|
|
60
|
+
expect(caller).toMatchObject({
|
|
61
|
+
userId,
|
|
62
|
+
role: 'admin',
|
|
34
63
|
ip: '127.0.0.1',
|
|
35
64
|
userAgent: 'Mozilla/5.0',
|
|
36
|
-
role: 'admin',
|
|
37
|
-
}));
|
|
38
|
-
});
|
|
39
|
-
it('emits identity.user.unauthorized_access when static role permission check fails', async () => {
|
|
40
|
-
const drizzle = mockDb({
|
|
41
|
-
execute: vi.fn().mockResolvedValue({
|
|
42
|
-
rows: [{ id: 'u1', role: 'support' }],
|
|
43
|
-
}),
|
|
44
65
|
});
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
const
|
|
49
|
-
|
|
66
|
+
expect(events.emit).not.toHaveBeenCalled();
|
|
67
|
+
});
|
|
68
|
+
it('passes the coarse check for an admin with no resource requested', async () => {
|
|
69
|
+
const userId = await seedUser('admin');
|
|
70
|
+
const { guard } = makeGuard({ userId });
|
|
71
|
+
await expect(guard.assert(requestContext(ADMIN_HEADERS))).resolves.toMatchObject({
|
|
72
|
+
role: 'admin',
|
|
50
73
|
});
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
'user-agent': 'Mozilla/5.0',
|
|
57
|
-
},
|
|
58
|
-
},
|
|
59
|
-
};
|
|
60
|
-
await expect(guard.assert(context, 'admin', 'delete')).rejects.toThrow(expect.objectContaining({ code: 'FORBIDDEN' }));
|
|
74
|
+
});
|
|
75
|
+
it('denies a support role a permission its static role lacks', async () => {
|
|
76
|
+
const userId = await seedUser('support');
|
|
77
|
+
const { guard, events } = makeGuard({ userId });
|
|
78
|
+
await expect(guard.assert(requestContext({ 'x-forwarded-for': '192.168.1.1, 10.0.0.1', 'user-agent': 'Mozilla/5.0' }), 'admin', 'delete')).rejects.toThrow(expect.objectContaining({ code: 'FORBIDDEN' }));
|
|
61
79
|
expect(events.emit).toHaveBeenCalledWith('identity.user.unauthorized_access', expect.objectContaining({
|
|
62
|
-
userId
|
|
80
|
+
userId,
|
|
63
81
|
resource: 'admin',
|
|
64
82
|
action: 'delete',
|
|
65
83
|
ip: '192.168.1.1',
|
|
66
|
-
userAgent: 'Mozilla/5.0',
|
|
67
84
|
role: 'support',
|
|
68
85
|
}));
|
|
69
86
|
});
|
|
70
|
-
it('
|
|
71
|
-
const
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
}),
|
|
75
|
-
});
|
|
76
|
-
const sessions = mock({
|
|
77
|
-
resolveUserId: vi.fn().mockResolvedValue('u1'),
|
|
78
|
-
});
|
|
79
|
-
const events = mock({
|
|
80
|
-
emit: vi.fn(),
|
|
81
|
-
});
|
|
82
|
-
const guard = new AdminGuard(drizzle, sessions, undefined, events);
|
|
83
|
-
const context = {
|
|
84
|
-
request: {
|
|
85
|
-
headers: {
|
|
86
|
-
'x-real-ip': '127.0.0.1',
|
|
87
|
-
'user-agent': 'Mozilla/5.0',
|
|
88
|
-
},
|
|
89
|
-
},
|
|
90
|
-
};
|
|
91
|
-
await expect(guard.assert(context, 'admin', 'delete')).rejects.toThrow(expect.objectContaining({ code: 'FORBIDDEN' }));
|
|
92
|
-
expect(events.emit).toHaveBeenCalledWith('identity.user.unauthorized_access', expect.objectContaining({
|
|
93
|
-
userId: 'u1',
|
|
94
|
-
resource: 'admin',
|
|
95
|
-
action: 'delete',
|
|
96
|
-
ip: '127.0.0.1',
|
|
97
|
-
userAgent: 'Mozilla/5.0',
|
|
98
|
-
role: 'player',
|
|
99
|
-
}));
|
|
87
|
+
it('allows a support role the permissions it does hold', async () => {
|
|
88
|
+
const userId = await seedUser('support');
|
|
89
|
+
const { guard } = makeGuard({ userId });
|
|
90
|
+
await expect(guard.assert(requestContext(ADMIN_HEADERS), 'player', 'view')).resolves.toMatchObject({ role: 'support' });
|
|
100
91
|
});
|
|
101
|
-
it('
|
|
102
|
-
const
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
expect(
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
}));
|
|
92
|
+
it('denies a role with no entry in the static table', async () => {
|
|
93
|
+
const userId = await seedUser('player');
|
|
94
|
+
const { guard, events } = makeGuard({ userId });
|
|
95
|
+
await expect(guard.assert(requestContext(ADMIN_HEADERS), 'admin', 'delete')).rejects.toThrow(expect.objectContaining({ code: 'FORBIDDEN' }));
|
|
96
|
+
expect(events.emit).toHaveBeenCalledWith('identity.user.unauthorized_access', expect.objectContaining({ userId, role: 'player', action: 'delete' }));
|
|
97
|
+
});
|
|
98
|
+
it('denies the coarse check for a non-admin role with the default admin:access pair', async () => {
|
|
99
|
+
const userId = await seedUser('player');
|
|
100
|
+
const { guard, events } = makeGuard({ userId });
|
|
101
|
+
await expect(guard.assert(requestContext(ADMIN_HEADERS))).rejects.toThrow(expect.objectContaining({ code: 'FORBIDDEN' }));
|
|
102
|
+
expect(events.emit).toHaveBeenCalledWith('identity.user.unauthorized_access', expect.objectContaining({ resource: 'admin', action: 'access', role: 'player' }));
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
describe('AdminGuard.assert - DB grants (real PG)', () => {
|
|
106
|
+
it('allows a permission the resolver grants', async () => {
|
|
107
|
+
const userId = await seedUser('admin');
|
|
108
|
+
const { guard } = makeGuard({ userId, grants: [{ resource: 'admin', action: 'delete' }] });
|
|
109
|
+
await expect(guard.assert(requestContext(ADMIN_HEADERS), 'admin', 'delete')).resolves.toMatchObject({ userId });
|
|
110
|
+
});
|
|
111
|
+
it('denies and emits when the resolver returns no matching grant', async () => {
|
|
112
|
+
const userId = await seedUser('admin');
|
|
113
|
+
const { guard, events } = makeGuard({ userId, grants: [] });
|
|
114
|
+
await expect(guard.assert(requestContext(ADMIN_HEADERS), 'admin', 'delete')).rejects.toThrow(expect.objectContaining({ code: 'FORBIDDEN' }));
|
|
115
|
+
expect(events.emit).toHaveBeenCalledWith('identity.user.unauthorized_access', expect.objectContaining({ userId, resource: 'admin', action: 'delete', role: 'admin' }));
|
|
116
|
+
});
|
|
117
|
+
it('lets DB grants override what the static role would have allowed', async () => {
|
|
118
|
+
const userId = await seedUser('admin');
|
|
119
|
+
const { guard } = makeGuard({ userId, grants: [{ resource: 'player', action: 'view' }] });
|
|
120
|
+
await expect(guard.assert(requestContext(ADMIN_HEADERS), 'admin', 'delete')).rejects.toThrow(expect.objectContaining({ code: 'FORBIDDEN' }));
|
|
131
121
|
});
|
|
132
122
|
});
|
|
133
123
|
//# sourceMappingURL=admin-guard.test.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"admin-guard.test.js","sourceRoot":"","sources":["../../../../src/server/auth/__tests__/admin-guard.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;
|
|
1
|
+
{"version":3,"file":"admin-guard.test.js","sourceRoot":"","sources":["../../../../src/server/auth/__tests__/admin-guard.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"permission-levels.test.d.ts","sourceRoot":"","sources":["../../../../src/server/auth/__tests__/permission-levels.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { levelRank, isLevelSufficient, levelToActions, actionsToLevel, PERMISSION_LEVELS, } from '../permission-levels.js';
|
|
3
|
+
import { statement } from '../permissions.js';
|
|
4
|
+
describe('levelRank', () => {
|
|
5
|
+
it('orders the levels from no_access up to read_write', () => {
|
|
6
|
+
expect(PERMISSION_LEVELS.map(levelRank)).toEqual([0, 1, 2]);
|
|
7
|
+
});
|
|
8
|
+
it('ranks an unknown level below every real one so it can never satisfy a check', () => {
|
|
9
|
+
expect(levelRank('owner')).toBe(-1);
|
|
10
|
+
});
|
|
11
|
+
});
|
|
12
|
+
describe('isLevelSufficient', () => {
|
|
13
|
+
it.each([
|
|
14
|
+
['no_access', 'no_access', true],
|
|
15
|
+
['no_access', 'read', false],
|
|
16
|
+
['no_access', 'read_write', false],
|
|
17
|
+
['read', 'no_access', true],
|
|
18
|
+
['read', 'read', true],
|
|
19
|
+
['read', 'read_write', false],
|
|
20
|
+
['read_write', 'no_access', true],
|
|
21
|
+
['read_write', 'read', true],
|
|
22
|
+
['read_write', 'read_write', true],
|
|
23
|
+
])('%s covering %s is %s', (have, required, expected) => {
|
|
24
|
+
expect(isLevelSufficient(have, required)).toBe(expected);
|
|
25
|
+
});
|
|
26
|
+
it('refuses to escalate an unknown level', () => {
|
|
27
|
+
expect(isLevelSufficient('owner', 'read')).toBe(false);
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
describe('levelToActions', () => {
|
|
31
|
+
it('grants nothing at no_access, whatever the resource', () => {
|
|
32
|
+
for (const resource of Object.keys(statement)) {
|
|
33
|
+
expect(levelToActions(resource, 'no_access')).toEqual([]);
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
it('grants every declared action at read_write', () => {
|
|
37
|
+
for (const [resource, actions] of Object.entries(statement)) {
|
|
38
|
+
expect(levelToActions(resource, 'read_write')).toEqual(actions);
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
it('grants only view at read', () => {
|
|
42
|
+
expect(levelToActions('player', 'read')).toEqual(['view']);
|
|
43
|
+
expect(levelToActions('withdrawal', 'read')).toEqual(['view']);
|
|
44
|
+
});
|
|
45
|
+
it('grants nothing at read for a resource that declares no view action', () => {
|
|
46
|
+
expect(statement.content).not.toContain('view');
|
|
47
|
+
expect(levelToActions('content', 'read')).toEqual([]);
|
|
48
|
+
});
|
|
49
|
+
it.each(PERMISSION_LEVELS)('grants nothing on an unknown resource at %s', (level) => {
|
|
50
|
+
expect(levelToActions('tournaments', level)).toEqual([]);
|
|
51
|
+
});
|
|
52
|
+
it('never returns an action the resource does not declare', () => {
|
|
53
|
+
for (const [resource, actions] of Object.entries(statement)) {
|
|
54
|
+
for (const level of PERMISSION_LEVELS) {
|
|
55
|
+
expect(actions).toEqual(expect.arrayContaining([...levelToActions(resource, level)]));
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
describe('actionsToLevel', () => {
|
|
61
|
+
it('round-trips every level for every resource that can express it', () => {
|
|
62
|
+
for (const [resource, actions] of Object.entries(statement)) {
|
|
63
|
+
const viewOnlyResource = actions.length === 1 && actions[0] === 'view';
|
|
64
|
+
for (const level of PERMISSION_LEVELS) {
|
|
65
|
+
const granted = levelToActions(resource, level);
|
|
66
|
+
const expected = granted.length === 0 ? 'no_access' : viewOnlyResource ? 'read_write' : level;
|
|
67
|
+
expect(actionsToLevel(resource, granted)).toBe(expected);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
it('cannot tell read from read_write on a resource whose only action is view', () => {
|
|
72
|
+
expect(statement.report).toEqual(['view']);
|
|
73
|
+
expect(actionsToLevel('report', ['view'])).toBe('read_write');
|
|
74
|
+
});
|
|
75
|
+
it('collapses a partial action set down, never up', () => {
|
|
76
|
+
expect(actionsToLevel('player', ['view', 'update'])).toBe('read');
|
|
77
|
+
expect(actionsToLevel('player', ['update', 'ban'])).toBe('no_access');
|
|
78
|
+
});
|
|
79
|
+
it('reports no_access for an unknown resource', () => {
|
|
80
|
+
expect(actionsToLevel('tournaments', ['view'])).toBe('no_access');
|
|
81
|
+
});
|
|
82
|
+
it('reports no_access for an empty action set', () => {
|
|
83
|
+
expect(actionsToLevel('player', [])).toBe('no_access');
|
|
84
|
+
});
|
|
85
|
+
it('ignores actions the resource does not declare', () => {
|
|
86
|
+
expect(actionsToLevel('report', ['view', 'delete-everything'])).toBe('read_write');
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
//# sourceMappingURL=permission-levels.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"permission-levels.test.js","sourceRoot":"","sources":["../../../../src/server/auth/__tests__/permission-levels.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,27 +1,59 @@
|
|
|
1
|
-
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
-
import {
|
|
1
|
+
import { describe, it, expect, vi, beforeAll, afterAll, beforeEach } from 'vitest';
|
|
2
|
+
import { randomUUID } from 'node:crypto';
|
|
3
|
+
import { createTestDb } from '@openora/core/testing';
|
|
3
4
|
import { SessionResolver } from '../session-resolver.js';
|
|
4
|
-
const { getSessionMock } = vi.hoisted(() => ({
|
|
5
|
-
|
|
5
|
+
const { createAuthMock, getSessionMock } = vi.hoisted(() => ({
|
|
6
|
+
createAuthMock: vi.fn(),
|
|
7
|
+
getSessionMock: vi.fn(),
|
|
6
8
|
}));
|
|
7
9
|
vi.mock('../auth.js', () => ({
|
|
8
|
-
createAuth:
|
|
10
|
+
createAuth: createAuthMock.mockImplementation(() => ({
|
|
11
|
+
api: { getSession: getSessionMock },
|
|
12
|
+
})),
|
|
9
13
|
}));
|
|
14
|
+
let db;
|
|
15
|
+
beforeAll(async () => {
|
|
16
|
+
db = await createTestDb([]);
|
|
17
|
+
});
|
|
18
|
+
afterAll(async () => {
|
|
19
|
+
await db.drop();
|
|
20
|
+
});
|
|
10
21
|
beforeEach(() => {
|
|
22
|
+
createAuthMock.mockClear();
|
|
11
23
|
getSessionMock.mockClear();
|
|
12
24
|
getSessionMock.mockResolvedValue(null);
|
|
13
25
|
});
|
|
14
|
-
describe('SessionResolver
|
|
26
|
+
describe('SessionResolver (real PG-backed drizzle)', () => {
|
|
27
|
+
it('hands the drizzle db and the injected schema to better-auth exactly once', () => {
|
|
28
|
+
const schema = { session: {} };
|
|
29
|
+
new SessionResolver(db.drizzle, schema);
|
|
30
|
+
expect(createAuthMock).toHaveBeenCalledTimes(1);
|
|
31
|
+
expect(createAuthMock).toHaveBeenCalledWith({ db: db.drizzle.db, schema });
|
|
32
|
+
});
|
|
33
|
+
it('omits the schema key when none is injected', () => {
|
|
34
|
+
new SessionResolver(db.drizzle);
|
|
35
|
+
expect(createAuthMock).toHaveBeenCalledWith({ db: db.drizzle.db });
|
|
36
|
+
});
|
|
15
37
|
it('returns undefined when there is no session', async () => {
|
|
16
|
-
const resolver = new SessionResolver(
|
|
17
|
-
|
|
18
|
-
expect(userId).toBeUndefined();
|
|
38
|
+
const resolver = new SessionResolver(db.drizzle);
|
|
39
|
+
expect(await resolver.resolveUserId(new Headers())).toBeUndefined();
|
|
19
40
|
});
|
|
20
41
|
it('returns the user id when a session resolves', async () => {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
const
|
|
24
|
-
expect(
|
|
42
|
+
const userId = randomUUID();
|
|
43
|
+
getSessionMock.mockResolvedValueOnce({ user: { id: userId } });
|
|
44
|
+
const resolver = new SessionResolver(db.drizzle);
|
|
45
|
+
expect(await resolver.resolveUserId(new Headers())).toBe(userId);
|
|
46
|
+
});
|
|
47
|
+
it('forwards the caller headers to better-auth', async () => {
|
|
48
|
+
const resolver = new SessionResolver(db.drizzle);
|
|
49
|
+
const headers = new Headers({ cookie: 'session=abc' });
|
|
50
|
+
await resolver.resolveUserId(headers);
|
|
51
|
+
expect(getSessionMock).toHaveBeenCalledWith({ headers });
|
|
52
|
+
});
|
|
53
|
+
it('returns undefined for a session without a user', async () => {
|
|
54
|
+
getSessionMock.mockResolvedValueOnce({});
|
|
55
|
+
const resolver = new SessionResolver(db.drizzle);
|
|
56
|
+
expect(await resolver.resolveUserId(new Headers())).toBeUndefined();
|
|
25
57
|
});
|
|
26
58
|
});
|
|
27
59
|
//# sourceMappingURL=session-resolver.test.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session-resolver.test.js","sourceRoot":"","sources":["../../../../src/server/auth/__tests__/session-resolver.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;
|
|
1
|
+
{"version":3,"file":"session-resolver.test.js","sourceRoot":"","sources":["../../../../src/server/auth/__tests__/session-resolver.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, expect, it } from 'vitest';
|
|
2
|
-
import { findOneOrThrow, pageToOffset } from '../query-helpers.js';
|
|
2
|
+
import { findOneOrThrow, pageToOffset, moneyToNumber, mapConcurrent } from '../query-helpers.js';
|
|
3
3
|
describe('findOneOrThrow', () => {
|
|
4
4
|
it('returns the first row when present', () => {
|
|
5
5
|
expect(findOneOrThrow([{ id: '1' }, { id: '2' }], new Error('nope'))).toEqual({ id: '1' });
|
|
@@ -15,4 +15,75 @@ describe('pageToOffset', () => {
|
|
|
15
15
|
expect(pageToOffset(3, 20)).toBe(40);
|
|
16
16
|
});
|
|
17
17
|
});
|
|
18
|
+
describe('moneyToNumber', () => {
|
|
19
|
+
it('reads a decimal money string', () => {
|
|
20
|
+
expect(moneyToNumber('100.50')).toBe(100.5);
|
|
21
|
+
});
|
|
22
|
+
it('reads a whole amount', () => {
|
|
23
|
+
expect(moneyToNumber('100')).toBe(100);
|
|
24
|
+
});
|
|
25
|
+
it('reads a zero amount', () => {
|
|
26
|
+
expect(moneyToNumber('0')).toBe(0);
|
|
27
|
+
expect(moneyToNumber('0.00')).toBe(0);
|
|
28
|
+
});
|
|
29
|
+
it('reads a negative amount', () => {
|
|
30
|
+
expect(moneyToNumber('-25.75')).toBe(-25.75);
|
|
31
|
+
});
|
|
32
|
+
it('keeps the ordering of two amounts that differ lexicographically', () => {
|
|
33
|
+
expect(moneyToNumber('9.00')).toBeLessThan(moneyToNumber('10.00'));
|
|
34
|
+
});
|
|
35
|
+
it('returns NaN for a non-numeric string rather than silently zeroing it', () => {
|
|
36
|
+
expect(moneyToNumber('abc')).toBeNaN();
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
describe('mapConcurrent', () => {
|
|
40
|
+
const identity = async (n) => n * 2;
|
|
41
|
+
it('returns results in input order, not completion order', async () => {
|
|
42
|
+
const delays = [30, 5, 20, 1];
|
|
43
|
+
const result = await mapConcurrent(delays, 2, async (ms, index) => {
|
|
44
|
+
await new Promise((resolve) => setTimeout(resolve, ms));
|
|
45
|
+
return index;
|
|
46
|
+
});
|
|
47
|
+
expect(result).toEqual([0, 1, 2, 3]);
|
|
48
|
+
});
|
|
49
|
+
it('passes the index alongside each item', async () => {
|
|
50
|
+
const seen = [];
|
|
51
|
+
await mapConcurrent(['a', 'b', 'c'], 2, async (item, index) => {
|
|
52
|
+
seen.push([item, index]);
|
|
53
|
+
});
|
|
54
|
+
expect(seen).toEqual([
|
|
55
|
+
['a', 0],
|
|
56
|
+
['b', 1],
|
|
57
|
+
['c', 2],
|
|
58
|
+
]);
|
|
59
|
+
});
|
|
60
|
+
it('never runs more than `concurrency` calls at once', async () => {
|
|
61
|
+
let inFlight = 0;
|
|
62
|
+
let peak = 0;
|
|
63
|
+
await mapConcurrent(Array.from({ length: 20 }, (_, i) => i), 3, async () => {
|
|
64
|
+
inFlight += 1;
|
|
65
|
+
peak = Math.max(peak, inFlight);
|
|
66
|
+
await new Promise((resolve) => setTimeout(resolve, 1));
|
|
67
|
+
inFlight -= 1;
|
|
68
|
+
});
|
|
69
|
+
expect(peak).toBe(3);
|
|
70
|
+
});
|
|
71
|
+
it('handles an empty list without spawning a worker', async () => {
|
|
72
|
+
expect(await mapConcurrent([], 5, identity)).toEqual([]);
|
|
73
|
+
});
|
|
74
|
+
it('caps the worker count at the item count', async () => {
|
|
75
|
+
expect(await mapConcurrent([1, 2], 100, identity)).toEqual([2, 4]);
|
|
76
|
+
});
|
|
77
|
+
it('runs serially when concurrency is below 1', async () => {
|
|
78
|
+
expect(await mapConcurrent([1, 2, 3], 0, identity)).toEqual([2, 4, 6]);
|
|
79
|
+
});
|
|
80
|
+
it('rejects when any item rejects', async () => {
|
|
81
|
+
await expect(mapConcurrent([1, 2, 3], 2, async (n) => {
|
|
82
|
+
if (n === 2) {
|
|
83
|
+
throw new Error('item 2 failed');
|
|
84
|
+
}
|
|
85
|
+
return n;
|
|
86
|
+
})).rejects.toThrow('item 2 failed');
|
|
87
|
+
});
|
|
88
|
+
});
|
|
18
89
|
//# sourceMappingURL=query-helpers.test.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"query-helpers.test.js","sourceRoot":"","sources":["../../../../src/server/db/__tests__/query-helpers.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"query-helpers.test.js","sourceRoot":"","sources":["../../../../src/server/db/__tests__/query-helpers.test.ts"],"names":[],"mappings":"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"}
|