@openora/core 0.4.1-canary.59 → 0.4.1-canary.62
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/plugin.d.ts +2 -0
- package/dist/admin-console/plugin.d.ts.map +1 -1
- package/dist/analytics/plugin.d.ts +2 -0
- package/dist/analytics/plugin.d.ts.map +1 -1
- package/dist/audit/plugin.d.ts +2 -0
- package/dist/audit/plugin.d.ts.map +1 -1
- package/dist/audit/plugin.js +13 -0
- package/dist/audit/plugin.js.map +1 -1
- package/dist/casino/gaming/plugin.d.ts +2 -0
- package/dist/casino/gaming/plugin.d.ts.map +1 -1
- package/dist/casino/lobby/plugin.d.ts +2 -0
- package/dist/casino/lobby/plugin.d.ts.map +1 -1
- package/dist/cms/plugin.d.ts +2 -0
- package/dist/cms/plugin.d.ts.map +1 -1
- package/dist/compliance/plugin.d.ts +2 -0
- package/dist/compliance/plugin.d.ts.map +1 -1
- package/dist/contracts/adapters/audit.d.ts +1 -1
- package/dist/contracts/adapters/audit.d.ts.map +1 -1
- package/dist/contracts/adapters/audit.js.map +1 -1
- package/dist/contracts/adapters/gift-commands.d.ts +54 -0
- package/dist/contracts/adapters/gift-commands.d.ts.map +1 -0
- package/dist/contracts/adapters/gift-commands.js +11 -0
- package/dist/contracts/adapters/gift-commands.js.map +1 -0
- package/dist/contracts/adapters/index.d.ts +4 -0
- package/dist/contracts/adapters/index.d.ts.map +1 -1
- package/dist/contracts/adapters/index.js +2 -0
- package/dist/contracts/adapters/index.js.map +1 -1
- package/dist/contracts/adapters/rain-commands.d.ts +23 -0
- package/dist/contracts/adapters/rain-commands.d.ts.map +1 -0
- package/dist/contracts/adapters/rain-commands.js +12 -0
- package/dist/contracts/adapters/rain-commands.js.map +1 -0
- package/dist/contracts/schemas/chat-command-metadata.d.ts +12 -0
- package/dist/contracts/schemas/chat-command-metadata.d.ts.map +1 -1
- package/dist/contracts/schemas/chat-command-metadata.js +10 -0
- package/dist/contracts/schemas/chat-command-metadata.js.map +1 -1
- package/dist/contracts/schemas/chat-command.d.ts +25 -0
- package/dist/contracts/schemas/chat-command.d.ts.map +1 -1
- package/dist/contracts/schemas/chat-command.js +13 -1
- package/dist/contracts/schemas/chat-command.js.map +1 -1
- package/dist/contracts/schemas/events.d.ts +15 -3
- package/dist/contracts/schemas/events.d.ts.map +1 -1
- package/dist/contracts/schemas/events.js +17 -6
- package/dist/contracts/schemas/events.js.map +1 -1
- package/dist/contracts/schemas/identity.d.ts +2 -1
- package/dist/contracts/schemas/identity.d.ts.map +1 -1
- package/dist/contracts/schemas/identity.js +5 -1
- package/dist/contracts/schemas/identity.js.map +1 -1
- package/dist/engagement/chat/__tests__/chat.service.int.test.js +211 -8
- package/dist/engagement/chat/__tests__/chat.service.int.test.js.map +1 -1
- package/dist/engagement/chat/contract/index.d.ts +154 -8
- package/dist/engagement/chat/contract/index.d.ts.map +1 -1
- package/dist/engagement/chat/contract/index.js +44 -2
- package/dist/engagement/chat/contract/index.js.map +1 -1
- package/dist/engagement/chat/drizzle/migrations/0004_panoramic_robin_chapel.sql +6 -0
- package/dist/engagement/chat/drizzle/migrations/meta/0004_snapshot.json +666 -0
- package/dist/engagement/chat/drizzle/migrations/meta/_journal.json +7 -0
- package/dist/engagement/chat/plugin.d.ts +2 -0
- package/dist/engagement/chat/plugin.d.ts.map +1 -1
- package/dist/engagement/chat/router/index.d.ts +126 -8
- package/dist/engagement/chat/router/index.d.ts.map +1 -1
- package/dist/engagement/chat/router/index.js +10 -2
- package/dist/engagement/chat/router/index.js.map +1 -1
- package/dist/engagement/chat/schema/index.d.ts +50 -0
- package/dist/engagement/chat/schema/index.d.ts.map +1 -1
- package/dist/engagement/chat/schema/index.js +15 -3
- package/dist/engagement/chat/schema/index.js.map +1 -1
- package/dist/engagement/chat/service/chat.service.d.ts +95 -9
- package/dist/engagement/chat/service/chat.service.d.ts.map +1 -1
- package/dist/engagement/chat/service/chat.service.js +115 -24
- package/dist/engagement/chat/service/chat.service.js.map +1 -1
- package/dist/engagement/chat-commands/contract/index.d.ts +143 -71
- package/dist/engagement/chat-commands/contract/index.d.ts.map +1 -1
- package/dist/engagement/chat-commands/contract/index.js +48 -70
- package/dist/engagement/chat-commands/contract/index.js.map +1 -1
- package/dist/engagement/chat-commands/plugin.d.ts +3 -1
- package/dist/engagement/chat-commands/plugin.d.ts.map +1 -1
- package/dist/engagement/chat-commands/plugin.js +4 -4
- package/dist/engagement/chat-commands/plugin.js.map +1 -1
- package/dist/engagement/chat-commands/router/index.d.ts +149 -83
- package/dist/engagement/chat-commands/router/index.d.ts.map +1 -1
- package/dist/engagement/chat-commands/router/index.js +36 -26
- package/dist/engagement/chat-commands/router/index.js.map +1 -1
- package/dist/engagement/chat-commands/seed/index.d.ts.map +1 -1
- package/dist/engagement/chat-commands/seed/index.js +12 -0
- package/dist/engagement/chat-commands/seed/index.js.map +1 -1
- package/dist/engagement/chat-commands/service/__tests__/chat-commands.service.test.js +197 -733
- package/dist/engagement/chat-commands/service/__tests__/chat-commands.service.test.js.map +1 -1
- package/dist/engagement/chat-commands/service/chat-commands.service.d.ts +43 -68
- package/dist/engagement/chat-commands/service/chat-commands.service.d.ts.map +1 -1
- package/dist/engagement/chat-commands/service/chat-commands.service.js +138 -571
- package/dist/engagement/chat-commands/service/chat-commands.service.js.map +1 -1
- package/dist/engagement/contracts.d.ts +1 -0
- package/dist/engagement/contracts.d.ts.map +1 -1
- package/dist/engagement/contracts.js +1 -0
- package/dist/engagement/contracts.js.map +1 -1
- package/dist/engagement/index.d.ts +1 -0
- package/dist/engagement/index.d.ts.map +1 -1
- package/dist/engagement/index.js +1 -0
- package/dist/engagement/index.js.map +1 -1
- package/dist/engagement/notifications/plugin.d.ts +2 -0
- package/dist/engagement/notifications/plugin.d.ts.map +1 -1
- package/dist/engagement/server.d.ts +1 -0
- package/dist/engagement/server.d.ts.map +1 -1
- package/dist/engagement/server.js +1 -0
- package/dist/engagement/server.js.map +1 -1
- package/dist/engagement/social-transfers/contract/index.d.ts +67 -0
- package/dist/engagement/social-transfers/contract/index.d.ts.map +1 -0
- package/dist/engagement/social-transfers/contract/index.js +21 -0
- package/dist/engagement/social-transfers/contract/index.js.map +1 -0
- package/dist/engagement/social-transfers/drizzle/migrations/0000_clammy_rattler.sql +46 -0
- package/dist/engagement/social-transfers/drizzle/migrations/meta/0000_snapshot.json +287 -0
- package/dist/engagement/social-transfers/drizzle/migrations/meta/_journal.json +13 -0
- package/dist/engagement/social-transfers/drizzle.config.d.ts +4 -0
- package/dist/engagement/social-transfers/drizzle.config.d.ts.map +1 -0
- package/dist/engagement/social-transfers/drizzle.config.js +21 -0
- package/dist/engagement/social-transfers/drizzle.config.js.map +1 -0
- package/dist/engagement/social-transfers/index.d.ts +3 -0
- package/dist/engagement/social-transfers/index.d.ts.map +1 -0
- package/dist/engagement/social-transfers/index.js +6 -0
- package/dist/engagement/social-transfers/index.js.map +1 -0
- package/dist/engagement/social-transfers/migrate.d.ts +5 -0
- package/dist/engagement/social-transfers/migrate.d.ts.map +1 -0
- package/dist/engagement/social-transfers/migrate.js +17 -0
- package/dist/engagement/social-transfers/migrate.js.map +1 -0
- package/dist/engagement/social-transfers/plugin.d.ts +111 -0
- package/dist/engagement/social-transfers/plugin.d.ts.map +1 -0
- package/dist/engagement/social-transfers/plugin.js +24 -0
- package/dist/engagement/social-transfers/plugin.js.map +1 -0
- package/dist/engagement/social-transfers/router/index.d.ts +59 -0
- package/dist/engagement/social-transfers/router/index.d.ts.map +1 -0
- package/dist/engagement/social-transfers/router/index.js +25 -0
- package/dist/engagement/social-transfers/router/index.js.map +1 -0
- package/dist/engagement/social-transfers/schema/index.d.ts +609 -0
- package/dist/engagement/social-transfers/schema/index.d.ts.map +1 -0
- package/dist/engagement/social-transfers/schema/index.js +61 -0
- package/dist/engagement/social-transfers/schema/index.js.map +1 -0
- package/dist/engagement/social-transfers/service/__tests__/social-transfers.service.test.d.ts +2 -0
- package/dist/engagement/social-transfers/service/__tests__/social-transfers.service.test.d.ts.map +1 -0
- package/dist/engagement/social-transfers/service/__tests__/social-transfers.service.test.js +672 -0
- package/dist/engagement/social-transfers/service/__tests__/social-transfers.service.test.js.map +1 -0
- package/dist/engagement/social-transfers/service/social-transfers.service.d.ts +72 -0
- package/dist/engagement/social-transfers/service/social-transfers.service.d.ts.map +1 -0
- package/dist/engagement/social-transfers/service/social-transfers.service.js +726 -0
- package/dist/engagement/social-transfers/service/social-transfers.service.js.map +1 -0
- package/dist/iam/plugin.d.ts +2 -0
- package/dist/iam/plugin.d.ts.map +1 -1
- package/dist/iam/service/iam.service.d.ts +1 -1
- package/dist/pam/identity/__tests__/identity.service.int.test.js +47 -2
- package/dist/pam/identity/__tests__/identity.service.int.test.js.map +1 -1
- package/dist/pam/identity/__tests__/phone-login.service.int.test.js +23 -2
- package/dist/pam/identity/__tests__/phone-login.service.int.test.js.map +1 -1
- package/dist/pam/identity/plugin.d.ts +2 -0
- package/dist/pam/identity/plugin.d.ts.map +1 -1
- package/dist/pam/identity/service/identity.service.d.ts.map +1 -1
- package/dist/pam/identity/service/identity.service.js +29 -1
- package/dist/pam/identity/service/identity.service.js.map +1 -1
- package/dist/pam/identity/service/phone-login.service.d.ts.map +1 -1
- package/dist/pam/identity/service/phone-login.service.js +23 -0
- package/dist/pam/identity/service/phone-login.service.js.map +1 -1
- package/dist/pam/player-management/__tests__/player.router.int.test.js +27 -4
- package/dist/pam/player-management/__tests__/player.router.int.test.js.map +1 -1
- package/dist/pam/player-management/__tests__/player.service.int.test.js +159 -6
- package/dist/pam/player-management/__tests__/player.service.int.test.js.map +1 -1
- package/dist/pam/player-management/contract/index.d.ts +39 -0
- package/dist/pam/player-management/contract/index.d.ts.map +1 -1
- package/dist/pam/player-management/contract/index.js +33 -1
- package/dist/pam/player-management/contract/index.js.map +1 -1
- package/dist/pam/player-management/plugin.d.ts +3 -1
- package/dist/pam/player-management/plugin.d.ts.map +1 -1
- package/dist/pam/player-management/plugin.js +3 -3
- package/dist/pam/player-management/plugin.js.map +1 -1
- package/dist/pam/player-management/router/index.d.ts +21 -0
- package/dist/pam/player-management/router/index.d.ts.map +1 -1
- package/dist/pam/player-management/router/index.js +12 -2
- package/dist/pam/player-management/router/index.js.map +1 -1
- package/dist/pam/player-management/service/player.service.d.ts +10 -3
- package/dist/pam/player-management/service/player.service.d.ts.map +1 -1
- package/dist/pam/player-management/service/player.service.js +66 -3
- package/dist/pam/player-management/service/player.service.js.map +1 -1
- package/dist/pam/player-note/__tests__/player-note.router.int.test.d.ts +2 -0
- package/dist/pam/player-note/__tests__/player-note.router.int.test.d.ts.map +1 -0
- package/dist/pam/player-note/__tests__/player-note.router.int.test.js +52 -0
- package/dist/pam/player-note/__tests__/player-note.router.int.test.js.map +1 -0
- package/dist/pam/player-note/plugin.d.ts +49 -46
- package/dist/pam/player-note/plugin.d.ts.map +1 -1
- package/dist/pam/player-note/plugin.js +3 -1
- package/dist/pam/player-note/plugin.js.map +1 -1
- package/dist/pam/player-note/router/index.d.ts +2 -1
- package/dist/pam/player-note/router/index.d.ts.map +1 -1
- package/dist/pam/player-note/router/index.js +13 -2
- package/dist/pam/player-note/router/index.js.map +1 -1
- package/dist/pam/profile/plugin.d.ts +2 -0
- package/dist/pam/profile/plugin.d.ts.map +1 -1
- package/dist/pam/tag/plugin.d.ts +2 -0
- package/dist/pam/tag/plugin.d.ts.map +1 -1
- package/dist/server/auth/permissions.d.ts +24 -0
- package/dist/server/auth/permissions.d.ts.map +1 -1
- package/dist/server/auth/permissions.js +4 -0
- package/dist/server/auth/permissions.js.map +1 -1
- package/dist/server/runtime/core-token-catalog.d.ts +2 -0
- package/dist/server/runtime/core-token-catalog.d.ts.map +1 -1
- package/dist/server/runtime/core-token-catalog.js +3 -1
- package/dist/server/runtime/core-token-catalog.js.map +1 -1
- package/dist/wallet/__tests__/wallet.service.int.test.js +7 -0
- package/dist/wallet/__tests__/wallet.service.int.test.js.map +1 -1
- package/dist/wallet/plugin.d.ts +2 -0
- package/dist/wallet/plugin.d.ts.map +1 -1
- package/dist/wallet/router/index.js +1 -1
- package/dist/wallet/router/index.js.map +1 -1
- package/dist/wallet/service/wallet.service.d.ts.map +1 -1
- package/dist/wallet/service/wallet.service.js +16 -0
- package/dist/wallet/service/wallet.service.js.map +1 -1
- package/package.json +21 -1
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { describe, it, expect, vi } from 'vitest';
|
|
2
|
-
import { mock, makeDrizzle
|
|
3
|
-
import { ChatCommandsService, CommandDisabledError, InsufficientBalanceError,
|
|
2
|
+
import { mock, makeDrizzle } from '../../../../testing/mock.js';
|
|
3
|
+
import { ChatCommandsService, CommandDisabledError, InsufficientBalanceError, NoOnlineUsersError, GiftNotFoundError, GiftAlreadyClaimedError, ChatRoomNotMemberError, } from '../chat-commands.service.js';
|
|
4
4
|
const ACTOR_ID = '00000000-0000-0000-0000-000000000001';
|
|
5
5
|
const CLAIMER_ID = '00000000-0000-0000-0000-000000000002';
|
|
6
6
|
const ROOM_ID = '00000000-0000-0000-0000-000000000003';
|
|
7
7
|
const MSG_ID = '00000000-0000-0000-0000-000000000004';
|
|
8
8
|
const GIFT_ID = '00000000-0000-0000-0000-000000000005';
|
|
9
|
-
const
|
|
9
|
+
const IDEMPOTENCY_KEY = '00000000-0000-0000-0000-0000000000aa';
|
|
10
10
|
const ENABLED_ROW = {
|
|
11
11
|
key: 'gift',
|
|
12
12
|
enabled: true,
|
|
@@ -31,69 +31,54 @@ const SYSTEM_MSG = {
|
|
|
31
31
|
},
|
|
32
32
|
createdAt: new Date().toISOString(),
|
|
33
33
|
};
|
|
34
|
-
|
|
35
|
-
|
|
34
|
+
function makeDirectory() {
|
|
35
|
+
return mock({
|
|
36
|
+
findPlayerIds: vi.fn().mockResolvedValue([ACTOR_ID]),
|
|
37
|
+
lookupPlayers: vi
|
|
38
|
+
.fn()
|
|
39
|
+
.mockImplementation((ids) => Promise.resolve(ids.filter((id) => id === ACTOR_ID).map((id) => ({ userId: id, username: 'bob' })))),
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
function makeBlockWriter(excluded = []) {
|
|
43
|
+
return mock({
|
|
44
|
+
blockUser: vi.fn().mockResolvedValue(undefined),
|
|
45
|
+
ignoreUser: vi.fn().mockResolvedValue(undefined),
|
|
46
|
+
getExcludedUserIds: vi.fn().mockResolvedValue(excluded),
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
const GIFT_STATE = {
|
|
36
50
|
id: GIFT_ID,
|
|
37
|
-
messageId: MSG_ID,
|
|
38
51
|
senderId: ACTOR_ID,
|
|
39
52
|
senderUsername: 'bob',
|
|
40
53
|
amount: '10.00000000',
|
|
41
54
|
currency: 'USD',
|
|
42
|
-
roomId: ROOM_ID,
|
|
43
55
|
claimedBy: null,
|
|
44
56
|
claimedByUsername: null,
|
|
45
57
|
claimedAt: null,
|
|
46
|
-
createdAt: new Date(),
|
|
58
|
+
createdAt: new Date().toISOString(),
|
|
47
59
|
};
|
|
48
|
-
function
|
|
60
|
+
function makeGiftCommands(overrides = {}) {
|
|
49
61
|
return mock({
|
|
50
|
-
|
|
62
|
+
sendGift: vi.fn().mockResolvedValue({ ok: true, message: SYSTEM_MSG }),
|
|
63
|
+
claimGift: vi.fn().mockResolvedValue({
|
|
64
|
+
ok: true,
|
|
65
|
+
claimedBy: CLAIMER_ID,
|
|
66
|
+
claimedByUsername: 'alice',
|
|
67
|
+
claimedAt: new Date().toISOString(),
|
|
68
|
+
}),
|
|
69
|
+
getGift: vi.fn().mockResolvedValue({ ok: true, gift: GIFT_STATE }),
|
|
70
|
+
...overrides,
|
|
51
71
|
});
|
|
52
72
|
}
|
|
53
|
-
function
|
|
73
|
+
function makeRainCommands(overrides = {}) {
|
|
54
74
|
return mock({
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
.mockResolvedValue(ok
|
|
58
|
-
? { ok: true, newBalance: '90.00000000', currency: 'USD' }
|
|
59
|
-
: { ok: false, available: '5.00000000' }),
|
|
60
|
-
credit: vi.fn().mockResolvedValue({ ok: true, newBalance: '110.00000000' }),
|
|
75
|
+
sendRain: vi.fn().mockResolvedValue({ ok: true, message: SYSTEM_MSG }),
|
|
76
|
+
...overrides,
|
|
61
77
|
});
|
|
62
78
|
}
|
|
63
|
-
|
|
64
|
-
function makeDirectory(senderUsername = 'bob', claimerUsername = 'alice') {
|
|
65
|
-
const all = [
|
|
66
|
-
{
|
|
67
|
-
userId: ACTOR_ID,
|
|
68
|
-
username: senderUsername,
|
|
69
|
-
email: 'bob@example.com',
|
|
70
|
-
kycStatus: null,
|
|
71
|
-
language: null,
|
|
72
|
-
avatarUrl: null,
|
|
73
|
-
createdAt: DIRECTORY_CREATED_AT,
|
|
74
|
-
level: 3,
|
|
75
|
-
currency: 'USD',
|
|
76
|
-
},
|
|
77
|
-
{
|
|
78
|
-
userId: CLAIMER_ID,
|
|
79
|
-
username: claimerUsername,
|
|
80
|
-
email: 'alice@example.com',
|
|
81
|
-
kycStatus: null,
|
|
82
|
-
language: null,
|
|
83
|
-
avatarUrl: null,
|
|
84
|
-
createdAt: DIRECTORY_CREATED_AT,
|
|
85
|
-
level: 1,
|
|
86
|
-
currency: 'USD',
|
|
87
|
-
},
|
|
88
|
-
];
|
|
79
|
+
function makeTransport(onlineIds = [CLAIMER_ID]) {
|
|
89
80
|
return mock({
|
|
90
|
-
|
|
91
|
-
lookupPlayers: vi.fn().mockImplementation((ids) => {
|
|
92
|
-
return Promise.resolve(all.filter((p) => ids.includes(p.userId)));
|
|
93
|
-
}),
|
|
94
|
-
getPlayerByUsername: vi.fn().mockImplementation((username) => {
|
|
95
|
-
return Promise.resolve(all.find((p) => p.username.toLowerCase() === username.toLowerCase()) ?? null);
|
|
96
|
-
}),
|
|
81
|
+
getOnlineUserIds: vi.fn().mockResolvedValue(onlineIds),
|
|
97
82
|
});
|
|
98
83
|
}
|
|
99
84
|
function makeAudit() {
|
|
@@ -102,71 +87,22 @@ function makeAudit() {
|
|
|
102
87
|
recordInTransaction: vi.fn().mockResolvedValue(undefined),
|
|
103
88
|
});
|
|
104
89
|
}
|
|
105
|
-
function makeBlockWriter() {
|
|
106
|
-
return mock({
|
|
107
|
-
blockUser: vi.fn().mockResolvedValue(undefined),
|
|
108
|
-
ignoreUser: vi.fn().mockResolvedValue(undefined),
|
|
109
|
-
getExcludedUserIds: vi.fn().mockResolvedValue([]),
|
|
110
|
-
});
|
|
111
|
-
}
|
|
112
|
-
function makeGameReporting() {
|
|
113
|
-
return mock({
|
|
114
|
-
getPlayerStats: vi.fn().mockResolvedValue({ totalWagered: '100.00000000', totalBets: 5 }),
|
|
115
|
-
});
|
|
116
|
-
}
|
|
117
|
-
function makeTransport(onlineIds = [CLAIMER_ID]) {
|
|
118
|
-
return mock({
|
|
119
|
-
getOnlineUserIds: vi.fn().mockResolvedValue(onlineIds),
|
|
120
|
-
publish: vi.fn().mockResolvedValue(undefined),
|
|
121
|
-
});
|
|
122
|
-
}
|
|
123
|
-
function makeRoomAccess() {
|
|
124
|
-
return mock({ verifyRoomAccess: vi.fn().mockResolvedValue(undefined) });
|
|
125
|
-
}
|
|
126
|
-
function makeCache(initial = {}) {
|
|
127
|
-
const values = new Map(Object.entries(initial));
|
|
128
|
-
return {
|
|
129
|
-
get: async (key) => values.get(key),
|
|
130
|
-
set: vi.fn(async (key, value) => {
|
|
131
|
-
values.set(key, value);
|
|
132
|
-
}),
|
|
133
|
-
setIfAbsent: vi.fn(async (key, value) => {
|
|
134
|
-
if (values.has(key)) {
|
|
135
|
-
return false;
|
|
136
|
-
}
|
|
137
|
-
values.set(key, value);
|
|
138
|
-
return true;
|
|
139
|
-
}),
|
|
140
|
-
delete: vi.fn(async (key) => {
|
|
141
|
-
for (const item of Array.isArray(key) ? key : [key]) {
|
|
142
|
-
values.delete(item);
|
|
143
|
-
}
|
|
144
|
-
}),
|
|
145
|
-
};
|
|
146
|
-
}
|
|
147
90
|
function makeSvc(overrides = {}) {
|
|
148
91
|
const drizzle = makeDrizzle({
|
|
149
92
|
select: overrides.drizzleRows?.select ?? [],
|
|
150
93
|
returning: overrides.drizzleRows?.returning ?? [],
|
|
151
|
-
execute: overrides.drizzleRows?.execute,
|
|
152
94
|
});
|
|
153
|
-
return new ChatCommandsService(drizzle, overrides.
|
|
95
|
+
return new ChatCommandsService(drizzle, overrides.directory ?? makeDirectory(), overrides.blockWriter ?? makeBlockWriter(), overrides.giftCommands ?? makeGiftCommands(), overrides.rainCommands ?? makeRainCommands(), overrides.transport ?? makeTransport(), overrides.audit ?? makeAudit());
|
|
154
96
|
}
|
|
155
97
|
describe('ChatCommandsService.listCommands', () => {
|
|
156
98
|
it('returns only enabled commands by default', async () => {
|
|
157
|
-
|
|
158
|
-
const svc = makeSvc({
|
|
159
|
-
drizzleRows: { select: [[ENABLED_ROW]] },
|
|
160
|
-
});
|
|
99
|
+
const svc = makeSvc({ drizzleRows: { select: [[ENABLED_ROW]] } });
|
|
161
100
|
const result = await svc.listCommands();
|
|
162
101
|
expect(result).toHaveLength(1);
|
|
163
102
|
expect(result[0]?.key).toBe('gift');
|
|
164
103
|
});
|
|
165
104
|
it('returns all commands when includeDisabled is true', async () => {
|
|
166
|
-
|
|
167
|
-
const svc = makeSvc({
|
|
168
|
-
drizzleRows: { select: [[ENABLED_ROW, DISABLED_ROW]] },
|
|
169
|
-
});
|
|
105
|
+
const svc = makeSvc({ drizzleRows: { select: [[ENABLED_ROW, DISABLED_ROW]] } });
|
|
170
106
|
const result = await svc.listCommands(true);
|
|
171
107
|
expect(result).toHaveLength(2);
|
|
172
108
|
});
|
|
@@ -189,700 +125,228 @@ describe('ChatCommandsService.searchMentions', () => {
|
|
|
189
125
|
});
|
|
190
126
|
it('excludes ids the viewer has blocked or ignored', async () => {
|
|
191
127
|
const directory = makeDirectory();
|
|
192
|
-
const blockWriter =
|
|
193
|
-
blockUser: vi.fn().mockResolvedValue(undefined),
|
|
194
|
-
ignoreUser: vi.fn().mockResolvedValue(undefined),
|
|
195
|
-
getExcludedUserIds: vi.fn().mockResolvedValue([ACTOR_ID]),
|
|
196
|
-
});
|
|
128
|
+
const blockWriter = makeBlockWriter([ACTOR_ID]);
|
|
197
129
|
const svc = makeSvc({ directory, blockWriter });
|
|
198
130
|
const result = await svc.searchMentions('bo', 5, CLAIMER_ID);
|
|
199
131
|
expect(blockWriter.getExcludedUserIds).toHaveBeenCalledWith(CLAIMER_ID);
|
|
200
132
|
expect(result).toEqual([]);
|
|
201
133
|
});
|
|
202
134
|
});
|
|
203
|
-
describe('ChatCommandsService.
|
|
204
|
-
it('returns
|
|
205
|
-
const
|
|
206
|
-
const svc = makeSvc({
|
|
207
|
-
const result = await svc.
|
|
208
|
-
expect(
|
|
209
|
-
expect(result).toEqual(
|
|
210
|
-
});
|
|
211
|
-
it('returns empty array when no matches', async () => {
|
|
212
|
-
const directory = mock({
|
|
213
|
-
findPlayerIds: vi.fn().mockResolvedValue([]),
|
|
214
|
-
lookupPlayers: vi.fn().mockResolvedValue([]),
|
|
215
|
-
});
|
|
216
|
-
const svc = makeSvc({ directory });
|
|
217
|
-
const result = await svc.searchPlayers('xyz', 10, CLAIMER_ID);
|
|
218
|
-
expect(result).toEqual([]);
|
|
219
|
-
});
|
|
220
|
-
it('excludes ids the viewer has blocked or ignored', async () => {
|
|
221
|
-
const directory = makeDirectory();
|
|
222
|
-
const blockWriter = mock({
|
|
223
|
-
blockUser: vi.fn().mockResolvedValue(undefined),
|
|
224
|
-
ignoreUser: vi.fn().mockResolvedValue(undefined),
|
|
225
|
-
getExcludedUserIds: vi.fn().mockResolvedValue([ACTOR_ID]),
|
|
226
|
-
});
|
|
227
|
-
const svc = makeSvc({ directory, blockWriter });
|
|
228
|
-
const result = await svc.searchPlayers('bo', 5, CLAIMER_ID);
|
|
229
|
-
expect(blockWriter.getExcludedUserIds).toHaveBeenCalledWith(CLAIMER_ID);
|
|
230
|
-
expect(result).toEqual([]);
|
|
231
|
-
});
|
|
232
|
-
});
|
|
233
|
-
describe('ChatCommandsService.getPlayerProfile', () => {
|
|
234
|
-
it('returns the full profile card on the happy path', async () => {
|
|
235
|
-
const gameReporting = makeGameReporting();
|
|
236
|
-
const svc = makeSvc({ directory: makeDirectory(), gameReporting });
|
|
237
|
-
const result = await svc.getPlayerProfile(ACTOR_ID, ACTOR_ID);
|
|
238
|
-
expect(gameReporting.getPlayerStats).toHaveBeenCalledWith(ACTOR_ID);
|
|
239
|
-
expect(result).toEqual({
|
|
240
|
-
userId: ACTOR_ID,
|
|
241
|
-
username: 'bob',
|
|
242
|
-
avatarUrl: null,
|
|
243
|
-
level: 3,
|
|
244
|
-
joinedAt: DIRECTORY_CREATED_AT.toISOString(),
|
|
245
|
-
totalWagered: '100.00000000',
|
|
246
|
-
totalBets: 5,
|
|
247
|
-
currency: 'USD',
|
|
248
|
-
});
|
|
249
|
-
});
|
|
250
|
-
it('throws ChatPlayerNotFoundError for an unknown userId', async () => {
|
|
251
|
-
const directory = mock({
|
|
252
|
-
lookupPlayers: vi.fn().mockResolvedValue([]),
|
|
253
|
-
});
|
|
254
|
-
const svc = makeSvc({ directory });
|
|
255
|
-
await expect(svc.getPlayerProfile(CLAIMER_ID, ACTOR_ID)).rejects.toThrow(ChatPlayerNotFoundError);
|
|
256
|
-
});
|
|
257
|
-
it('redacts private profile fields for another viewer', async () => {
|
|
258
|
-
const gameReporting = makeGameReporting();
|
|
259
|
-
const svc = makeSvc({ directory: makeDirectory(), gameReporting });
|
|
260
|
-
const result = await svc.getPlayerProfile(ACTOR_ID, CLAIMER_ID);
|
|
261
|
-
expect(result).toMatchObject({
|
|
262
|
-
userId: ACTOR_ID,
|
|
263
|
-
username: 'bob',
|
|
264
|
-
avatarUrl: null,
|
|
265
|
-
level: 3,
|
|
266
|
-
joinedAt: null,
|
|
267
|
-
totalWagered: null,
|
|
268
|
-
totalBets: null,
|
|
269
|
-
currency: null,
|
|
270
|
-
});
|
|
271
|
-
expect(gameReporting.getPlayerStats).not.toHaveBeenCalled();
|
|
135
|
+
describe('ChatCommandsService.postGift (delegates to GIFT_COMMANDS)', () => {
|
|
136
|
+
it('returns the message from the port on success', async () => {
|
|
137
|
+
const giftCommands = makeGiftCommands();
|
|
138
|
+
const svc = makeSvc({ giftCommands });
|
|
139
|
+
const result = await svc.postGift({ amount: '10.00000000', roomId: ROOM_ID, idempotencyKey: IDEMPOTENCY_KEY }, ACTOR_ID);
|
|
140
|
+
expect(giftCommands.sendGift).toHaveBeenCalledWith({ amount: '10.00000000', roomId: ROOM_ID, idempotencyKey: IDEMPOTENCY_KEY }, ACTOR_ID);
|
|
141
|
+
expect(result).toEqual(SYSTEM_MSG);
|
|
272
142
|
});
|
|
273
|
-
|
|
274
|
-
const
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
describe('ChatCommandsService.executeCommand (gift)', () => {
|
|
279
|
-
it('throws CommandDisabledError when command row is missing', async () => {
|
|
280
|
-
const svc = makeSvc({ drizzleRows: { select: [[]] } });
|
|
281
|
-
await expect(svc.executeCommand({ type: 'gift', amount: '10', roomId: ROOM_ID, idempotencyKey: IDEMPOTENCY_KEY }, ACTOR_ID)).rejects.toThrow(CommandDisabledError);
|
|
282
|
-
});
|
|
283
|
-
it('throws CommandDisabledError when command is disabled', async () => {
|
|
284
|
-
const svc = makeSvc({ drizzleRows: { select: [[DISABLED_ROW]] } });
|
|
285
|
-
await expect(svc.executeCommand({ type: 'gift', amount: '10', roomId: ROOM_ID, idempotencyKey: IDEMPOTENCY_KEY }, ACTOR_ID)).rejects.toThrow(CommandDisabledError);
|
|
286
|
-
});
|
|
287
|
-
it('throws InsufficientBalanceError when wallet debit fails', async () => {
|
|
288
|
-
const svc = makeSvc({
|
|
289
|
-
drizzleRows: {
|
|
290
|
-
select: [[ENABLED_ROW]],
|
|
291
|
-
// Only the idempotency guard insert is reached; wallet fails before the gift insert.
|
|
292
|
-
returning: [[{ id: IDEMPOTENCY_ROW_ID }]],
|
|
293
|
-
},
|
|
294
|
-
wallet: makeWallet(false),
|
|
143
|
+
it('maps a "disabled" port result to CommandDisabledError', async () => {
|
|
144
|
+
const giftCommands = makeGiftCommands({
|
|
145
|
+
sendGift: vi
|
|
146
|
+
.fn()
|
|
147
|
+
.mockResolvedValue({ ok: false, reason: 'disabled' }),
|
|
295
148
|
});
|
|
296
|
-
|
|
149
|
+
const svc = makeSvc({ giftCommands });
|
|
150
|
+
await expect(svc.postGift({ amount: '10.00000000', roomId: ROOM_ID, idempotencyKey: IDEMPOTENCY_KEY }, ACTOR_ID)).rejects.toThrow(CommandDisabledError);
|
|
297
151
|
});
|
|
298
|
-
it('
|
|
299
|
-
const
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
// First returning: idempotency guard insert; second: chatGift insert.
|
|
304
|
-
returning: [[{ ...GIFT_ROW }]],
|
|
305
|
-
},
|
|
306
|
-
writer,
|
|
152
|
+
it('maps an "insufficient_balance" port result to InsufficientBalanceError', async () => {
|
|
153
|
+
const giftCommands = makeGiftCommands({
|
|
154
|
+
sendGift: vi
|
|
155
|
+
.fn()
|
|
156
|
+
.mockResolvedValue({ ok: false, reason: 'insufficient_balance' }),
|
|
307
157
|
});
|
|
308
|
-
const
|
|
309
|
-
expect(
|
|
310
|
-
expect(writer.postSystemMessage).toHaveBeenCalledWith(expect.objectContaining({
|
|
311
|
-
metadata: expect.objectContaining({
|
|
312
|
-
command: 'gift',
|
|
313
|
-
giftId: GIFT_ID,
|
|
314
|
-
senderId: ACTOR_ID,
|
|
315
|
-
senderUsername: 'bob',
|
|
316
|
-
}),
|
|
317
|
-
}));
|
|
318
|
-
expect(result.id).toBe(MSG_ID);
|
|
158
|
+
const svc = makeSvc({ giftCommands });
|
|
159
|
+
await expect(svc.postGift({ amount: '10.00000000', roomId: ROOM_ID, idempotencyKey: IDEMPOTENCY_KEY }, ACTOR_ID)).rejects.toThrow(InsufficientBalanceError);
|
|
319
160
|
});
|
|
320
|
-
it('
|
|
321
|
-
const
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
161
|
+
it('maps a "room_not_member" port result to ChatRoomNotMemberError', async () => {
|
|
162
|
+
const giftCommands = makeGiftCommands({
|
|
163
|
+
sendGift: vi
|
|
164
|
+
.fn()
|
|
165
|
+
.mockResolvedValue({ ok: false, reason: 'room_not_member' }),
|
|
325
166
|
});
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
amount: '1.00000000',
|
|
329
|
-
roomId: ROOM_ID,
|
|
330
|
-
idempotencyKey: IDEMPOTENCY_KEY,
|
|
331
|
-
}, ACTOR_ID)).rejects.toThrow(BelowMinimumError);
|
|
167
|
+
const svc = makeSvc({ giftCommands });
|
|
168
|
+
await expect(svc.postGift({ amount: '10.00000000', roomId: ROOM_ID, idempotencyKey: IDEMPOTENCY_KEY }, ACTOR_ID)).rejects.toThrow(ChatRoomNotMemberError);
|
|
332
169
|
});
|
|
333
170
|
});
|
|
334
|
-
describe('ChatCommandsService.
|
|
335
|
-
it('
|
|
336
|
-
const
|
|
337
|
-
const svc = makeSvc({
|
|
338
|
-
drizzleRows: {
|
|
339
|
-
select: [[ENABLED_ROW], []],
|
|
340
|
-
returning: [[{ id: IDEMPOTENCY_ROW_ID }], [{ ...GIFT_ROW }]],
|
|
341
|
-
},
|
|
342
|
-
wallet,
|
|
343
|
-
});
|
|
344
|
-
const result = await svc.executeCommand({ type: 'gift', amount: '10.00000000', roomId: ROOM_ID, idempotencyKey: IDEMPOTENCY_KEY }, ACTOR_ID);
|
|
345
|
-
expect(wallet.debit).toHaveBeenCalledOnce();
|
|
346
|
-
expect(result.id).toBe(MSG_ID);
|
|
347
|
-
});
|
|
348
|
-
it('replays the stored result without debiting the wallet again', async () => {
|
|
349
|
-
const wallet = makeWallet();
|
|
350
|
-
const giftInput = {
|
|
351
|
-
type: 'gift',
|
|
352
|
-
amount: '10.00000000',
|
|
353
|
-
roomId: ROOM_ID,
|
|
354
|
-
idempotencyKey: IDEMPOTENCY_KEY,
|
|
355
|
-
};
|
|
356
|
-
const storedRow = {
|
|
357
|
-
id: IDEMPOTENCY_ROW_ID,
|
|
358
|
-
actorId: ACTOR_ID,
|
|
359
|
-
commandType: 'gift',
|
|
360
|
-
idempotencyKey: IDEMPOTENCY_KEY,
|
|
361
|
-
amount: '10.00000000',
|
|
362
|
-
fingerprint: fingerprintCommand(giftInput),
|
|
363
|
-
result: SYSTEM_MSG,
|
|
364
|
-
createdAt: new Date(),
|
|
365
|
-
};
|
|
366
|
-
const svc = makeSvc({
|
|
367
|
-
drizzleRows: { select: [[ENABLED_ROW]] },
|
|
368
|
-
cache: makeCache({
|
|
369
|
-
[IDEMPOTENCY_CACHE_KEY]: { fingerprint: storedRow.fingerprint, result: SYSTEM_MSG },
|
|
370
|
-
}),
|
|
371
|
-
wallet,
|
|
372
|
-
});
|
|
373
|
-
const result = await svc.executeCommand(giftInput, ACTOR_ID);
|
|
374
|
-
expect(wallet.debit).not.toHaveBeenCalled();
|
|
375
|
-
expect(result).toEqual(SYSTEM_MSG);
|
|
376
|
-
});
|
|
377
|
-
it('throws ChatCommandIdempotencyKeyReuseError when the same key is reused with a different amount', async () => {
|
|
378
|
-
const storedRow = {
|
|
379
|
-
id: IDEMPOTENCY_ROW_ID,
|
|
380
|
-
actorId: ACTOR_ID,
|
|
381
|
-
commandType: 'gift',
|
|
382
|
-
idempotencyKey: IDEMPOTENCY_KEY,
|
|
383
|
-
amount: '5.00000000',
|
|
384
|
-
fingerprint: fingerprintCommand({
|
|
385
|
-
type: 'gift',
|
|
386
|
-
amount: '5.00000000',
|
|
387
|
-
roomId: ROOM_ID,
|
|
388
|
-
idempotencyKey: IDEMPOTENCY_KEY,
|
|
389
|
-
}),
|
|
390
|
-
result: SYSTEM_MSG,
|
|
391
|
-
createdAt: new Date(),
|
|
392
|
-
};
|
|
393
|
-
const svc = makeSvc({
|
|
394
|
-
drizzleRows: { select: [[ENABLED_ROW]] },
|
|
395
|
-
cache: makeCache({
|
|
396
|
-
[IDEMPOTENCY_CACHE_KEY]: { fingerprint: storedRow.fingerprint, result: SYSTEM_MSG },
|
|
397
|
-
}),
|
|
398
|
-
});
|
|
399
|
-
await expect(svc.executeCommand({ type: 'gift', amount: '10.00000000', roomId: ROOM_ID, idempotencyKey: IDEMPOTENCY_KEY }, ACTOR_ID)).rejects.toThrow(ChatCommandIdempotencyKeyReuseError);
|
|
400
|
-
});
|
|
401
|
-
it('throws ChatCommandIdempotencyKeyReuseError when the same key+amount is reused for a different room', async () => {
|
|
402
|
-
const storedRow = {
|
|
403
|
-
id: IDEMPOTENCY_ROW_ID,
|
|
404
|
-
actorId: ACTOR_ID,
|
|
405
|
-
commandType: 'gift',
|
|
406
|
-
idempotencyKey: IDEMPOTENCY_KEY,
|
|
407
|
-
amount: '10.00000000',
|
|
408
|
-
fingerprint: fingerprintCommand({
|
|
409
|
-
type: 'gift',
|
|
410
|
-
amount: '10.00000000',
|
|
411
|
-
roomId: OTHER_ROOM_ID,
|
|
412
|
-
idempotencyKey: IDEMPOTENCY_KEY,
|
|
413
|
-
}),
|
|
414
|
-
result: SYSTEM_MSG,
|
|
415
|
-
createdAt: new Date(),
|
|
416
|
-
};
|
|
417
|
-
const svc = makeSvc({
|
|
418
|
-
drizzleRows: { select: [[ENABLED_ROW]] },
|
|
419
|
-
cache: makeCache({
|
|
420
|
-
[IDEMPOTENCY_CACHE_KEY]: { fingerprint: storedRow.fingerprint, result: SYSTEM_MSG },
|
|
421
|
-
}),
|
|
422
|
-
});
|
|
423
|
-
await expect(svc.executeCommand({ type: 'gift', amount: '10.00000000', roomId: ROOM_ID, idempotencyKey: IDEMPOTENCY_KEY }, ACTOR_ID)).rejects.toThrow(ChatCommandIdempotencyKeyReuseError);
|
|
424
|
-
});
|
|
425
|
-
});
|
|
426
|
-
describe('ChatCommandsService.claimGift', () => {
|
|
427
|
-
it('credits the claimer and returns claim info on happy path', async () => {
|
|
428
|
-
const wallet = makeWallet();
|
|
429
|
-
const svc = makeSvc({
|
|
430
|
-
drizzleRows: {
|
|
431
|
-
// select[0]: gift lookup; returning[0]: atomic update result
|
|
432
|
-
select: [[GIFT_ROW]],
|
|
433
|
-
returning: [
|
|
434
|
-
[
|
|
435
|
-
{
|
|
436
|
-
...GIFT_ROW,
|
|
437
|
-
claimedBy: CLAIMER_ID,
|
|
438
|
-
claimedByUsername: 'alice',
|
|
439
|
-
claimedAt: new Date(),
|
|
440
|
-
},
|
|
441
|
-
],
|
|
442
|
-
],
|
|
443
|
-
},
|
|
444
|
-
wallet,
|
|
445
|
-
});
|
|
171
|
+
describe('ChatCommandsService.claimGift (delegates to GIFT_COMMANDS)', () => {
|
|
172
|
+
it('returns the claim info from the port on success', async () => {
|
|
173
|
+
const giftCommands = makeGiftCommands();
|
|
174
|
+
const svc = makeSvc({ giftCommands });
|
|
446
175
|
const result = await svc.claimGift(GIFT_ID, CLAIMER_ID);
|
|
447
|
-
expect(
|
|
448
|
-
expect(wallet.credit).toHaveBeenCalledWith(expect.anything(), expect.objectContaining({ userId: CLAIMER_ID, type: 'gift' }));
|
|
176
|
+
expect(giftCommands.claimGift).toHaveBeenCalledWith(GIFT_ID, CLAIMER_ID);
|
|
449
177
|
expect(result.claimedBy).toBe(CLAIMER_ID);
|
|
450
178
|
expect(result.claimedByUsername).toBe('alice');
|
|
451
|
-
expect(result.claimedAt).toEqual(expect.any(String));
|
|
452
179
|
});
|
|
453
|
-
it('
|
|
454
|
-
const
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
},
|
|
180
|
+
it('maps a "gift_not_found" port result to GiftNotFoundError', async () => {
|
|
181
|
+
const giftCommands = makeGiftCommands({
|
|
182
|
+
claimGift: vi
|
|
183
|
+
.fn()
|
|
184
|
+
.mockResolvedValue({ ok: false, reason: 'gift_not_found' }),
|
|
459
185
|
});
|
|
460
|
-
|
|
186
|
+
const svc = makeSvc({ giftCommands });
|
|
187
|
+
await expect(svc.claimGift(GIFT_ID, CLAIMER_ID)).rejects.toThrow(GiftNotFoundError);
|
|
461
188
|
});
|
|
462
|
-
it('
|
|
463
|
-
const
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
},
|
|
189
|
+
it('maps an "already_claimed" port result to GiftAlreadyClaimedError', async () => {
|
|
190
|
+
const giftCommands = makeGiftCommands({
|
|
191
|
+
claimGift: vi
|
|
192
|
+
.fn()
|
|
193
|
+
.mockResolvedValue({ ok: false, reason: 'already_claimed' }),
|
|
468
194
|
});
|
|
195
|
+
const svc = makeSvc({ giftCommands });
|
|
469
196
|
await expect(svc.claimGift(GIFT_ID, CLAIMER_ID)).rejects.toThrow(GiftAlreadyClaimedError);
|
|
470
197
|
});
|
|
471
|
-
it('
|
|
472
|
-
const
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
await expect(svc.claimGift(GIFT_ID, CLAIMER_ID)).rejects.toThrow(GiftNotFoundError);
|
|
479
|
-
});
|
|
480
|
-
});
|
|
481
|
-
describe('ChatCommandsService.adminUpdateCommand', () => {
|
|
482
|
-
it('upserts the command row and returns the descriptor', async () => {
|
|
483
|
-
const updatedRow = { ...ENABLED_ROW, key: 'gift', enabled: false };
|
|
484
|
-
const svc = makeSvc({
|
|
485
|
-
drizzleRows: { returning: [[updatedRow]] },
|
|
198
|
+
it('maps a "room_not_member" port result to ChatRoomNotMemberError using the port\'s roomId, not the giftId', async () => {
|
|
199
|
+
const giftCommands = makeGiftCommands({
|
|
200
|
+
claimGift: vi.fn().mockResolvedValue({
|
|
201
|
+
ok: false,
|
|
202
|
+
reason: 'room_not_member',
|
|
203
|
+
roomId: ROOM_ID,
|
|
204
|
+
}),
|
|
486
205
|
});
|
|
487
|
-
const
|
|
488
|
-
expect(
|
|
489
|
-
expect(result.enabled).toBe(false);
|
|
490
|
-
});
|
|
491
|
-
it('records an audit entry on update', async () => {
|
|
492
|
-
const updatedRow = { ...ENABLED_ROW, key: 'rain', enabled: false };
|
|
493
|
-
const audit = makeAudit();
|
|
494
|
-
const drizzle = makeDrizzle({ select: [], returning: [[updatedRow]] });
|
|
495
|
-
const svcWithAudit = new ChatCommandsService(drizzle, makeWriter(), makeWallet(), makeDirectory(), audit, makeTransport(), mock(makeEventBus()), makeBlockWriter(), makeGameReporting(), makeRoomAccess(), makeCache());
|
|
496
|
-
await svcWithAudit.adminUpdateCommand({ key: 'rain', enabled: false }, ACTOR_ID);
|
|
497
|
-
expect(audit.record).toHaveBeenCalledWith(expect.objectContaining({
|
|
498
|
-
actorId: ACTOR_ID,
|
|
499
|
-
actorType: 'admin',
|
|
500
|
-
action: 'chat.command.updated',
|
|
501
|
-
resourceId: 'rain',
|
|
502
|
-
}));
|
|
206
|
+
const svc = makeSvc({ giftCommands });
|
|
207
|
+
await expect(svc.claimGift(GIFT_ID, CLAIMER_ID)).rejects.toThrow(new ChatRoomNotMemberError(ROOM_ID));
|
|
503
208
|
});
|
|
504
209
|
});
|
|
505
|
-
describe('ChatCommandsService.
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
const svc = makeSvc({
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
}
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
amount: '10.99000000',
|
|
528
|
-
fingerprint: fingerprintCommand({
|
|
529
|
-
type: 'rain',
|
|
530
|
-
amount: '10.99000000',
|
|
531
|
-
recipientCount: 10,
|
|
210
|
+
describe('ChatCommandsService.getGift (delegates to GIFT_COMMANDS)', () => {
|
|
211
|
+
it('returns the gift state from the port on success', async () => {
|
|
212
|
+
const giftCommands = makeGiftCommands();
|
|
213
|
+
const svc = makeSvc({ giftCommands });
|
|
214
|
+
const result = await svc.getGift(GIFT_ID, CLAIMER_ID);
|
|
215
|
+
expect(giftCommands.getGift).toHaveBeenCalledWith(GIFT_ID, CLAIMER_ID);
|
|
216
|
+
expect(result).toEqual(GIFT_STATE);
|
|
217
|
+
});
|
|
218
|
+
it('maps a "gift_not_found" port result to GiftNotFoundError', async () => {
|
|
219
|
+
const giftCommands = makeGiftCommands({
|
|
220
|
+
getGift: vi
|
|
221
|
+
.fn()
|
|
222
|
+
.mockResolvedValue({ ok: false, reason: 'gift_not_found' }),
|
|
223
|
+
});
|
|
224
|
+
const svc = makeSvc({ giftCommands });
|
|
225
|
+
await expect(svc.getGift(GIFT_ID, CLAIMER_ID)).rejects.toThrow(GiftNotFoundError);
|
|
226
|
+
});
|
|
227
|
+
it('maps a "room_not_member" port result to ChatRoomNotMemberError using the port\'s roomId', async () => {
|
|
228
|
+
const giftCommands = makeGiftCommands({
|
|
229
|
+
getGift: vi.fn().mockResolvedValue({
|
|
230
|
+
ok: false,
|
|
231
|
+
reason: 'room_not_member',
|
|
532
232
|
roomId: ROOM_ID,
|
|
533
|
-
idempotencyKey: IDEMPOTENCY_KEY,
|
|
534
|
-
}),
|
|
535
|
-
result: SYSTEM_MSG,
|
|
536
|
-
createdAt: new Date(),
|
|
537
|
-
};
|
|
538
|
-
const svc = makeSvc({
|
|
539
|
-
drizzleRows: { select: [[RAIN_ROW]] },
|
|
540
|
-
transport,
|
|
541
|
-
cache: makeCache({
|
|
542
|
-
[RAIN_IDEMPOTENCY_CACHE_KEY]: { fingerprint: storedRow.fingerprint, result: SYSTEM_MSG },
|
|
543
233
|
}),
|
|
544
234
|
});
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
amount: '10.99000000',
|
|
548
|
-
recipientCount: 10,
|
|
549
|
-
roomId: ROOM_ID,
|
|
550
|
-
idempotencyKey: IDEMPOTENCY_KEY,
|
|
551
|
-
}, ACTOR_ID)).resolves.toEqual(SYSTEM_MSG);
|
|
552
|
-
expect(transport.getOnlineUserIds).not.toHaveBeenCalled();
|
|
235
|
+
const svc = makeSvc({ giftCommands });
|
|
236
|
+
await expect(svc.getGift(GIFT_ID, CLAIMER_ID)).rejects.toThrow(new ChatRoomNotMemberError(ROOM_ID));
|
|
553
237
|
});
|
|
554
|
-
|
|
238
|
+
});
|
|
239
|
+
describe('ChatCommandsService.postRain (resolves presence, delegates to RAIN_COMMANDS)', () => {
|
|
240
|
+
it('resolves online user ids via CHAT_REALTIME_TRANSPORT and passes them to the port', async () => {
|
|
555
241
|
const RECIPIENT_2 = '00000000-0000-0000-0000-000000000006';
|
|
556
|
-
const
|
|
557
|
-
const
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
returning: [[{ id: IDEMPOTENCY_ROW_ID }]],
|
|
561
|
-
execute: [[{ per_recipient: '5.00000000', total_distributed: '10.00000000' }]],
|
|
562
|
-
},
|
|
563
|
-
wallet,
|
|
564
|
-
transport: makeTransport([ACTOR_ID, CLAIMER_ID, RECIPIENT_2]),
|
|
565
|
-
});
|
|
566
|
-
await svc.executeCommand({
|
|
567
|
-
type: 'rain',
|
|
242
|
+
const transport = makeTransport([ACTOR_ID, CLAIMER_ID, RECIPIENT_2]);
|
|
243
|
+
const rainCommands = makeRainCommands();
|
|
244
|
+
const svc = makeSvc({ transport, rainCommands });
|
|
245
|
+
const result = await svc.postRain({
|
|
568
246
|
amount: '10.00000000',
|
|
569
247
|
recipientCount: 2,
|
|
570
248
|
roomId: ROOM_ID,
|
|
571
249
|
idempotencyKey: IDEMPOTENCY_KEY,
|
|
572
250
|
}, ACTOR_ID);
|
|
573
|
-
expect(
|
|
574
|
-
expect(
|
|
575
|
-
userId: CLAIMER_ID,
|
|
576
|
-
amount: '5.00000000',
|
|
577
|
-
currency: 'USD',
|
|
578
|
-
type: 'rain',
|
|
579
|
-
});
|
|
580
|
-
});
|
|
581
|
-
it('uses Postgres floor division to avoid float imprecision', async () => {
|
|
582
|
-
const wallet = makeWallet();
|
|
583
|
-
const svc = makeSvc({
|
|
584
|
-
drizzleRows: {
|
|
585
|
-
select: [[RAIN_ROW]],
|
|
586
|
-
returning: [[{ id: IDEMPOTENCY_ROW_ID }]],
|
|
587
|
-
execute: [[{ per_recipient: '3.33333333', total_distributed: '9.99999999' }]],
|
|
588
|
-
},
|
|
589
|
-
wallet,
|
|
590
|
-
transport: makeTransport([
|
|
591
|
-
CLAIMER_ID,
|
|
592
|
-
'00000000-0000-0000-0000-000000000006',
|
|
593
|
-
'00000000-0000-0000-0000-000000000007',
|
|
594
|
-
]),
|
|
595
|
-
});
|
|
596
|
-
await svc.executeCommand({
|
|
597
|
-
type: 'rain',
|
|
598
|
-
amount: '10.00000000',
|
|
599
|
-
recipientCount: 3,
|
|
600
|
-
roomId: ROOM_ID,
|
|
601
|
-
idempotencyKey: IDEMPOTENCY_KEY,
|
|
602
|
-
}, ACTOR_ID);
|
|
603
|
-
expect(wallet.credit).toHaveBeenCalledWith(expect.anything(), {
|
|
604
|
-
userId: expect.any(String),
|
|
605
|
-
amount: '3.33333333',
|
|
606
|
-
currency: 'USD',
|
|
607
|
-
type: 'rain',
|
|
608
|
-
});
|
|
609
|
-
});
|
|
610
|
-
it('debits only the amount actually distributed (floor(amount/n)*n), never the raw player-typed amount', async () => {
|
|
611
|
-
const wallet = makeWallet();
|
|
612
|
-
const svc = makeSvc({
|
|
613
|
-
drizzleRows: {
|
|
614
|
-
select: [[RAIN_ROW]],
|
|
615
|
-
returning: [[{ id: IDEMPOTENCY_ROW_ID }]],
|
|
616
|
-
execute: [[{ per_recipient: '1.00000000', total_distributed: '10.00000000' }]],
|
|
617
|
-
},
|
|
618
|
-
wallet,
|
|
619
|
-
transport: makeTransport(Array.from({ length: 10 }, (_, i) => `00000000-0000-0000-0000-0000000000${10 + i}`)),
|
|
620
|
-
});
|
|
621
|
-
await svc.executeCommand({
|
|
622
|
-
type: 'rain',
|
|
623
|
-
amount: '10.99000000',
|
|
624
|
-
recipientCount: 10,
|
|
625
|
-
roomId: ROOM_ID,
|
|
626
|
-
idempotencyKey: IDEMPOTENCY_KEY,
|
|
627
|
-
}, ACTOR_ID);
|
|
628
|
-
expect(wallet.debit).toHaveBeenCalledWith(expect.anything(), {
|
|
629
|
-
userId: ACTOR_ID,
|
|
630
|
-
amount: '10.00000000',
|
|
631
|
-
type: 'rain',
|
|
632
|
-
});
|
|
633
|
-
});
|
|
634
|
-
it('throws BelowMinimumError when amount is below config minAmount', async () => {
|
|
635
|
-
const svc = makeSvc({
|
|
636
|
-
drizzleRows: {
|
|
637
|
-
select: [[{ ...RAIN_ROW, config: { minAmount: '5.00000000' } }]],
|
|
638
|
-
},
|
|
639
|
-
});
|
|
640
|
-
await expect(svc.executeCommand({
|
|
641
|
-
type: 'rain',
|
|
642
|
-
amount: '1.00000000',
|
|
643
|
-
recipientCount: 1,
|
|
644
|
-
roomId: ROOM_ID,
|
|
645
|
-
idempotencyKey: IDEMPOTENCY_KEY,
|
|
646
|
-
}, ACTOR_ID)).rejects.toThrow(BelowMinimumError);
|
|
647
|
-
});
|
|
648
|
-
it('throws ExceedsLimitError when amount is above config maxAmount', async () => {
|
|
649
|
-
const svc = makeSvc({
|
|
650
|
-
drizzleRows: {
|
|
651
|
-
select: [[{ ...RAIN_ROW, config: { maxAmount: '10.00000000' } }]],
|
|
652
|
-
},
|
|
653
|
-
});
|
|
654
|
-
await expect(svc.executeCommand({
|
|
655
|
-
type: 'rain',
|
|
656
|
-
amount: '50.00000000',
|
|
657
|
-
recipientCount: 1,
|
|
658
|
-
roomId: ROOM_ID,
|
|
659
|
-
idempotencyKey: IDEMPOTENCY_KEY,
|
|
660
|
-
}, ACTOR_ID)).rejects.toThrow(ExceedsLimitError);
|
|
661
|
-
});
|
|
662
|
-
it('throws ExceedsLimitError when recipientCount exceeds config maxRecipients', async () => {
|
|
663
|
-
const svc = makeSvc({
|
|
664
|
-
drizzleRows: {
|
|
665
|
-
select: [[{ ...RAIN_ROW, config: { maxRecipients: 10 } }]],
|
|
666
|
-
},
|
|
667
|
-
});
|
|
668
|
-
await expect(svc.executeCommand({
|
|
669
|
-
type: 'rain',
|
|
670
|
-
amount: '100.00000000',
|
|
671
|
-
recipientCount: 11,
|
|
672
|
-
roomId: ROOM_ID,
|
|
673
|
-
idempotencyKey: IDEMPOTENCY_KEY,
|
|
674
|
-
}, ACTOR_ID)).rejects.toThrow(ExceedsLimitError);
|
|
675
|
-
});
|
|
676
|
-
it('throws TooManyRecipientsError when recipientCount exceeds the whole-dollar amount', async () => {
|
|
677
|
-
const svc = makeSvc({
|
|
678
|
-
drizzleRows: { select: [[RAIN_ROW]] },
|
|
679
|
-
});
|
|
680
|
-
await expect(svc.executeCommand({
|
|
681
|
-
type: 'rain',
|
|
682
|
-
amount: '3.00000000',
|
|
683
|
-
recipientCount: 4,
|
|
684
|
-
roomId: ROOM_ID,
|
|
685
|
-
idempotencyKey: IDEMPOTENCY_KEY,
|
|
686
|
-
}, ACTOR_ID)).rejects.toThrow(TooManyRecipientsError);
|
|
687
|
-
});
|
|
688
|
-
});
|
|
689
|
-
const DONATE_ROW = {
|
|
690
|
-
key: 'donate',
|
|
691
|
-
enabled: true,
|
|
692
|
-
label: 'Donate',
|
|
693
|
-
description: 'Send a direct tip to a specific player',
|
|
694
|
-
config: null,
|
|
695
|
-
updatedAt: new Date(),
|
|
696
|
-
};
|
|
697
|
-
const DONATE_SYSTEM_MSG = {
|
|
698
|
-
id: MSG_ID,
|
|
699
|
-
roomId: ROOM_ID,
|
|
700
|
-
actorId: ACTOR_ID,
|
|
701
|
-
content: '',
|
|
702
|
-
metadata: {
|
|
703
|
-
command: 'donate',
|
|
704
|
-
recipientId: CLAIMER_ID,
|
|
705
|
-
recipientUsername: 'alice',
|
|
706
|
-
amount: '10.00000000',
|
|
707
|
-
currency: 'USD',
|
|
708
|
-
},
|
|
709
|
-
createdAt: new Date().toISOString(),
|
|
710
|
-
};
|
|
711
|
-
/**
|
|
712
|
-
* Directory mock where findPlayerIds resolves the RECIPIENT (CLAIMER_ID = 'alice').
|
|
713
|
-
* Used for donate tests that target 'alice' — the default makeDirectory() always
|
|
714
|
-
* returns ACTOR_ID which only has username 'bob', so alice is never found.
|
|
715
|
-
*/
|
|
716
|
-
function makeRecipientDirectory() {
|
|
717
|
-
const all = [
|
|
718
|
-
{
|
|
719
|
-
userId: ACTOR_ID,
|
|
720
|
-
username: 'bob',
|
|
721
|
-
email: 'bob@example.com',
|
|
722
|
-
kycStatus: null,
|
|
723
|
-
language: null,
|
|
724
|
-
avatarUrl: null,
|
|
725
|
-
createdAt: DIRECTORY_CREATED_AT,
|
|
726
|
-
level: 3,
|
|
727
|
-
currency: 'USD',
|
|
728
|
-
},
|
|
729
|
-
{
|
|
730
|
-
userId: CLAIMER_ID,
|
|
731
|
-
username: 'alice',
|
|
732
|
-
email: 'alice@example.com',
|
|
733
|
-
kycStatus: null,
|
|
734
|
-
language: null,
|
|
735
|
-
avatarUrl: null,
|
|
736
|
-
createdAt: DIRECTORY_CREATED_AT,
|
|
737
|
-
level: 1,
|
|
738
|
-
currency: 'USD',
|
|
739
|
-
},
|
|
740
|
-
];
|
|
741
|
-
return mock({
|
|
742
|
-
findPlayerIds: vi.fn().mockResolvedValue([CLAIMER_ID]),
|
|
743
|
-
lookupPlayers: vi.fn().mockImplementation((ids) => {
|
|
744
|
-
return Promise.resolve(all.filter((p) => ids.includes(p.userId)));
|
|
745
|
-
}),
|
|
746
|
-
getPlayerByUsername: vi.fn().mockImplementation((username) => {
|
|
747
|
-
return Promise.resolve(all.find((p) => p.username.toLowerCase() === username.toLowerCase()) ?? null);
|
|
748
|
-
}),
|
|
749
|
-
});
|
|
750
|
-
}
|
|
751
|
-
describe('ChatCommandsService.handleDonate', () => {
|
|
752
|
-
it('debits sender, credits recipient, and returns system message on success', async () => {
|
|
753
|
-
const wallet = makeWallet();
|
|
754
|
-
const writer = mock({
|
|
755
|
-
postSystemMessage: vi.fn().mockResolvedValue(DONATE_SYSTEM_MSG),
|
|
756
|
-
});
|
|
757
|
-
const svc = makeSvc({
|
|
758
|
-
drizzleRows: {
|
|
759
|
-
select: [[DONATE_ROW]],
|
|
760
|
-
returning: [[{ id: IDEMPOTENCY_ROW_ID }]],
|
|
761
|
-
},
|
|
762
|
-
wallet,
|
|
763
|
-
writer,
|
|
764
|
-
directory: makeRecipientDirectory(),
|
|
765
|
-
});
|
|
766
|
-
const result = await svc.executeCommand({
|
|
767
|
-
type: 'donate',
|
|
768
|
-
targetUsername: 'alice',
|
|
251
|
+
expect(transport.getOnlineUserIds).toHaveBeenCalledWith(`chat:room:${ROOM_ID}`);
|
|
252
|
+
expect(rainCommands.sendRain).toHaveBeenCalledWith({
|
|
769
253
|
amount: '10.00000000',
|
|
254
|
+
recipientCount: 2,
|
|
770
255
|
roomId: ROOM_ID,
|
|
771
256
|
idempotencyKey: IDEMPOTENCY_KEY,
|
|
257
|
+
onlineUserIds: [ACTOR_ID, CLAIMER_ID, RECIPIENT_2],
|
|
772
258
|
}, ACTOR_ID);
|
|
773
|
-
expect(
|
|
774
|
-
userId: ACTOR_ID,
|
|
775
|
-
amount: '10.00000000',
|
|
776
|
-
type: 'tip',
|
|
777
|
-
});
|
|
778
|
-
expect(wallet.credit).toHaveBeenCalledWith(expect.anything(), {
|
|
779
|
-
userId: CLAIMER_ID,
|
|
780
|
-
amount: '10.00000000',
|
|
781
|
-
currency: 'USD',
|
|
782
|
-
type: 'tip',
|
|
783
|
-
});
|
|
784
|
-
expect(result.id).toBe(MSG_ID);
|
|
259
|
+
expect(result).toEqual(SYSTEM_MSG);
|
|
785
260
|
});
|
|
786
|
-
it('
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
261
|
+
it('maps a "no_online_users" port result to NoOnlineUsersError', async () => {
|
|
262
|
+
const rainCommands = makeRainCommands({
|
|
263
|
+
sendRain: vi
|
|
264
|
+
.fn()
|
|
265
|
+
.mockResolvedValue({ ok: false, reason: 'no_online_users' }),
|
|
790
266
|
});
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
targetUsername: 'bob',
|
|
267
|
+
const svc = makeSvc({ rainCommands, transport: makeTransport([ACTOR_ID]) });
|
|
268
|
+
await expect(svc.postRain({
|
|
794
269
|
amount: '10.00000000',
|
|
270
|
+
recipientCount: 2,
|
|
795
271
|
roomId: ROOM_ID,
|
|
796
272
|
idempotencyKey: IDEMPOTENCY_KEY,
|
|
797
|
-
}, ACTOR_ID)).rejects.toThrow(
|
|
273
|
+
}, ACTOR_ID)).rejects.toThrow(NoOnlineUsersError);
|
|
798
274
|
});
|
|
799
|
-
it('
|
|
800
|
-
const
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
275
|
+
it('maps a "disabled" port result to CommandDisabledError', async () => {
|
|
276
|
+
const rainCommands = makeRainCommands({
|
|
277
|
+
sendRain: vi
|
|
278
|
+
.fn()
|
|
279
|
+
.mockResolvedValue({ ok: false, reason: 'disabled' }),
|
|
804
280
|
});
|
|
805
|
-
const svc = makeSvc({
|
|
806
|
-
|
|
807
|
-
directory,
|
|
808
|
-
});
|
|
809
|
-
await expect(svc.executeCommand({
|
|
810
|
-
type: 'donate',
|
|
811
|
-
targetUsername: 'ghost',
|
|
281
|
+
const svc = makeSvc({ rainCommands });
|
|
282
|
+
await expect(svc.postRain({
|
|
812
283
|
amount: '10.00000000',
|
|
284
|
+
recipientCount: 2,
|
|
813
285
|
roomId: ROOM_ID,
|
|
814
286
|
idempotencyKey: IDEMPOTENCY_KEY,
|
|
815
|
-
}, ACTOR_ID)).rejects.toThrow(
|
|
287
|
+
}, ACTOR_ID)).rejects.toThrow(CommandDisabledError);
|
|
816
288
|
});
|
|
817
|
-
it('
|
|
818
|
-
const
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
},
|
|
823
|
-
wallet: makeWallet(false),
|
|
824
|
-
directory: makeRecipientDirectory(),
|
|
289
|
+
it('maps a "room_not_member" port result to ChatRoomNotMemberError', async () => {
|
|
290
|
+
const rainCommands = makeRainCommands({
|
|
291
|
+
sendRain: vi
|
|
292
|
+
.fn()
|
|
293
|
+
.mockResolvedValue({ ok: false, reason: 'room_not_member' }),
|
|
825
294
|
});
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
targetUsername: 'alice',
|
|
295
|
+
const svc = makeSvc({ rainCommands });
|
|
296
|
+
await expect(svc.postRain({
|
|
829
297
|
amount: '10.00000000',
|
|
298
|
+
recipientCount: 2,
|
|
830
299
|
roomId: ROOM_ID,
|
|
831
300
|
idempotencyKey: IDEMPOTENCY_KEY,
|
|
832
|
-
}, ACTOR_ID)).rejects.toThrow(
|
|
833
|
-
});
|
|
834
|
-
it('throws BelowMinimumError when amount is below config minAmount', async () => {
|
|
835
|
-
// Amount check fires before directory lookup, so directory mock does not matter here.
|
|
836
|
-
const svc = makeSvc({
|
|
837
|
-
drizzleRows: {
|
|
838
|
-
select: [[{ ...DONATE_ROW, config: { minAmount: '5.00000000' } }]],
|
|
839
|
-
},
|
|
840
|
-
});
|
|
841
|
-
await expect(svc.executeCommand({
|
|
842
|
-
type: 'donate',
|
|
843
|
-
targetUsername: 'alice',
|
|
844
|
-
amount: '1.00000000',
|
|
845
|
-
roomId: ROOM_ID,
|
|
846
|
-
idempotencyKey: IDEMPOTENCY_KEY,
|
|
847
|
-
}, ACTOR_ID)).rejects.toThrow(BelowMinimumError);
|
|
301
|
+
}, ACTOR_ID)).rejects.toThrow(ChatRoomNotMemberError);
|
|
848
302
|
});
|
|
849
303
|
});
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
describe('ChatCommandsService.handleBlockAction', () => {
|
|
853
|
-
it('dispatches "block" to blockWriter.blockUser, not ignoreUser', async () => {
|
|
854
|
-
const blockWriter = makeBlockWriter();
|
|
304
|
+
describe('ChatCommandsService.adminListCommands', () => {
|
|
305
|
+
it('returns disabled commands too, unlike listCommands', async () => {
|
|
855
306
|
const svc = makeSvc({
|
|
856
|
-
drizzleRows: { select: [[
|
|
857
|
-
directory: makeRecipientDirectory(),
|
|
858
|
-
blockWriter,
|
|
307
|
+
drizzleRows: { select: [[ENABLED_ROW, DISABLED_ROW], [{ n: 2 }]] },
|
|
859
308
|
});
|
|
860
|
-
await svc.
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
const blockWriter = makeBlockWriter();
|
|
866
|
-
const svc = makeSvc({
|
|
867
|
-
drizzleRows: { select: [[IGNORE_ROW]] },
|
|
868
|
-
directory: makeRecipientDirectory(),
|
|
869
|
-
blockWriter,
|
|
309
|
+
const result = await svc.adminListCommands({
|
|
310
|
+
page: 1,
|
|
311
|
+
limit: 10,
|
|
312
|
+
sortBy: 'key',
|
|
313
|
+
sortOrder: 'asc',
|
|
870
314
|
});
|
|
871
|
-
|
|
872
|
-
expect(
|
|
873
|
-
expect(
|
|
315
|
+
expect(result.items).toHaveLength(2);
|
|
316
|
+
expect(result.total).toBe(2);
|
|
317
|
+
expect(result.items.map((c) => c.enabled)).toEqual([true, false]);
|
|
874
318
|
});
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
319
|
+
});
|
|
320
|
+
describe('ChatCommandsService.adminUpdateCommand', () => {
|
|
321
|
+
it('creates a new config row when none exists', async () => {
|
|
322
|
+
const row = { ...ENABLED_ROW, key: 'donate', label: 'Donate' };
|
|
323
|
+
const svc = makeSvc({ drizzleRows: { returning: [[row]] } });
|
|
324
|
+
const result = await svc.adminUpdateCommand({ key: 'donate', enabled: true }, ACTOR_ID);
|
|
325
|
+
expect(result).toMatchObject({ key: 'donate', label: 'Donate', enabled: true });
|
|
326
|
+
});
|
|
327
|
+
it('updates an existing config row', async () => {
|
|
328
|
+
const row = { ...ENABLED_ROW, enabled: false, config: { maxAmount: '100.00000000' } };
|
|
329
|
+
const svc = makeSvc({ drizzleRows: { returning: [[row]] } });
|
|
330
|
+
const result = await svc.adminUpdateCommand({ key: 'gift', enabled: false, config: { maxAmount: '100.00000000' } }, ACTOR_ID);
|
|
331
|
+
expect(result).toMatchObject({ key: 'gift', enabled: false });
|
|
332
|
+
});
|
|
333
|
+
it('records an audit entry describing the change', async () => {
|
|
334
|
+
const audit = makeAudit();
|
|
335
|
+
const svc = makeSvc({ audit, drizzleRows: { returning: [[ENABLED_ROW]] } });
|
|
336
|
+
await svc.adminUpdateCommand({ key: 'gift', enabled: true, config: { maxAmount: '50.00000000' } }, ACTOR_ID);
|
|
337
|
+
expect(audit.record).toHaveBeenCalledWith({
|
|
338
|
+
actorId: ACTOR_ID,
|
|
339
|
+
actorType: 'admin',
|
|
340
|
+
action: 'chat.command.updated',
|
|
341
|
+
resourceType: 'chat_command',
|
|
342
|
+
resourceId: 'gift',
|
|
343
|
+
before: null,
|
|
344
|
+
after: { enabled: true, config: { maxAmount: '50.00000000' } },
|
|
878
345
|
});
|
|
879
|
-
await expect(svc.executeCommand({ type: 'block', targetUsername: 'bob', roomId: ROOM_ID }, ACTOR_ID)).rejects.toThrow(SelfModerationActionError);
|
|
880
346
|
});
|
|
881
|
-
it('throws
|
|
882
|
-
const svc = makeSvc({
|
|
883
|
-
|
|
884
|
-
});
|
|
885
|
-
await expect(svc.executeCommand({ type: 'ignore', targetUsername: 'bob', roomId: ROOM_ID }, ACTOR_ID)).rejects.toThrow(SelfModerationActionError);
|
|
347
|
+
it('throws CommandDisabledError when the insert/update returns no row', async () => {
|
|
348
|
+
const svc = makeSvc({ drizzleRows: { returning: [[]] } });
|
|
349
|
+
await expect(svc.adminUpdateCommand({ key: 'gift', enabled: true }, ACTOR_ID)).rejects.toThrow(CommandDisabledError);
|
|
886
350
|
});
|
|
887
351
|
});
|
|
888
352
|
//# sourceMappingURL=chat-commands.service.test.js.map
|