@openora/core 0.4.1-canary.60 → 0.4.1-canary.63
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/chat-system-writer.d.ts +7 -1
- package/dist/contracts/adapters/chat-system-writer.d.ts.map +1 -1
- package/dist/contracts/adapters/chat-system-writer.js.map +1 -1
- package/dist/contracts/adapters/gift-commands.d.ts +58 -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 +5 -1
- 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 +27 -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 +26 -0
- package/dist/contracts/schemas/chat-command-metadata.d.ts.map +1 -1
- package/dist/contracts/schemas/chat-command-metadata.js +25 -0
- package/dist/contracts/schemas/chat-command-metadata.js.map +1 -1
- package/dist/contracts/schemas/chat-command.d.ts +126 -0
- package/dist/contracts/schemas/chat-command.d.ts.map +1 -1
- package/dist/contracts/schemas/chat-command.js +19 -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 +719 -40
- package/dist/engagement/chat/contract/index.d.ts.map +1 -1
- package/dist/engagement/chat/contract/index.js +51 -5
- 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 +608 -34
- 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 +72 -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 +421 -34
- package/dist/engagement/chat/service/chat.service.d.ts.map +1 -1
- package/dist/engagement/chat/service/chat.service.js +155 -40
- package/dist/engagement/chat/service/chat.service.js.map +1 -1
- package/dist/engagement/chat-commands/contract/index.d.ts +181 -73
- package/dist/engagement/chat-commands/contract/index.d.ts.map +1 -1
- package/dist/engagement/chat-commands/contract/index.js +50 -72
- 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 +185 -83
- package/dist/engagement/chat-commands/router/index.d.ts.map +1 -1
- package/dist/engagement/chat-commands/router/index.js +35 -25
- 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 +205 -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 +44 -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 +143 -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 +85 -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 +77 -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 +757 -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 +75 -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 +789 -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,
|
|
@@ -20,7 +20,11 @@ const SYSTEM_MSG = {
|
|
|
20
20
|
id: MSG_ID,
|
|
21
21
|
roomId: ROOM_ID,
|
|
22
22
|
actorId: ACTOR_ID,
|
|
23
|
+
userId: ACTOR_ID,
|
|
24
|
+
username: 'system',
|
|
23
25
|
content: '',
|
|
26
|
+
type: 'system',
|
|
27
|
+
isDeleted: false,
|
|
24
28
|
metadata: {
|
|
25
29
|
command: 'gift',
|
|
26
30
|
giftId: GIFT_ID,
|
|
@@ -28,72 +32,61 @@ const SYSTEM_MSG = {
|
|
|
28
32
|
senderUsername: 'bob',
|
|
29
33
|
amount: '10.00000000',
|
|
30
34
|
currency: 'USD',
|
|
35
|
+
status: 'available',
|
|
36
|
+
claimedBy: null,
|
|
37
|
+
claimedByUsername: null,
|
|
38
|
+
claimedAt: null,
|
|
31
39
|
},
|
|
32
40
|
createdAt: new Date().toISOString(),
|
|
33
41
|
};
|
|
34
|
-
|
|
35
|
-
|
|
42
|
+
function makeDirectory() {
|
|
43
|
+
return mock({
|
|
44
|
+
findPlayerIds: vi.fn().mockResolvedValue([ACTOR_ID]),
|
|
45
|
+
lookupPlayers: vi
|
|
46
|
+
.fn()
|
|
47
|
+
.mockImplementation((ids) => Promise.resolve(ids.filter((id) => id === ACTOR_ID).map((id) => ({ userId: id, username: 'bob' })))),
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
function makeBlockWriter(excluded = []) {
|
|
51
|
+
return mock({
|
|
52
|
+
blockUser: vi.fn().mockResolvedValue(undefined),
|
|
53
|
+
ignoreUser: vi.fn().mockResolvedValue(undefined),
|
|
54
|
+
getExcludedUserIds: vi.fn().mockResolvedValue(excluded),
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
const GIFT_STATE = {
|
|
36
58
|
id: GIFT_ID,
|
|
37
|
-
messageId: MSG_ID,
|
|
38
59
|
senderId: ACTOR_ID,
|
|
39
60
|
senderUsername: 'bob',
|
|
40
61
|
amount: '10.00000000',
|
|
41
62
|
currency: 'USD',
|
|
42
|
-
roomId: ROOM_ID,
|
|
43
63
|
claimedBy: null,
|
|
44
64
|
claimedByUsername: null,
|
|
45
65
|
claimedAt: null,
|
|
46
|
-
createdAt: new Date(),
|
|
66
|
+
createdAt: new Date().toISOString(),
|
|
47
67
|
};
|
|
48
|
-
function
|
|
68
|
+
function makeGiftCommands(overrides = {}) {
|
|
49
69
|
return mock({
|
|
50
|
-
|
|
70
|
+
sendGift: vi.fn().mockResolvedValue({ ok: true, message: SYSTEM_MSG }),
|
|
71
|
+
claimGift: vi.fn().mockResolvedValue({
|
|
72
|
+
ok: true,
|
|
73
|
+
claimedBy: CLAIMER_ID,
|
|
74
|
+
claimedByUsername: 'alice',
|
|
75
|
+
claimedAt: new Date().toISOString(),
|
|
76
|
+
}),
|
|
77
|
+
getGift: vi.fn().mockResolvedValue({ ok: true, gift: GIFT_STATE }),
|
|
78
|
+
...overrides,
|
|
51
79
|
});
|
|
52
80
|
}
|
|
53
|
-
function
|
|
81
|
+
function makeRainCommands(overrides = {}) {
|
|
54
82
|
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' }),
|
|
83
|
+
sendRain: vi.fn().mockResolvedValue({ ok: true, message: SYSTEM_MSG }),
|
|
84
|
+
...overrides,
|
|
61
85
|
});
|
|
62
86
|
}
|
|
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
|
-
];
|
|
87
|
+
function makeTransport(onlineIds = [CLAIMER_ID]) {
|
|
89
88
|
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
|
-
}),
|
|
89
|
+
getOnlineUserIds: vi.fn().mockResolvedValue(onlineIds),
|
|
97
90
|
});
|
|
98
91
|
}
|
|
99
92
|
function makeAudit() {
|
|
@@ -102,71 +95,22 @@ function makeAudit() {
|
|
|
102
95
|
recordInTransaction: vi.fn().mockResolvedValue(undefined),
|
|
103
96
|
});
|
|
104
97
|
}
|
|
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
98
|
function makeSvc(overrides = {}) {
|
|
148
99
|
const drizzle = makeDrizzle({
|
|
149
100
|
select: overrides.drizzleRows?.select ?? [],
|
|
150
101
|
returning: overrides.drizzleRows?.returning ?? [],
|
|
151
|
-
execute: overrides.drizzleRows?.execute,
|
|
152
102
|
});
|
|
153
|
-
return new ChatCommandsService(drizzle, overrides.
|
|
103
|
+
return new ChatCommandsService(drizzle, overrides.directory ?? makeDirectory(), overrides.blockWriter ?? makeBlockWriter(), overrides.giftCommands ?? makeGiftCommands(), overrides.rainCommands ?? makeRainCommands(), overrides.transport ?? makeTransport(), overrides.audit ?? makeAudit());
|
|
154
104
|
}
|
|
155
105
|
describe('ChatCommandsService.listCommands', () => {
|
|
156
106
|
it('returns only enabled commands by default', async () => {
|
|
157
|
-
|
|
158
|
-
const svc = makeSvc({
|
|
159
|
-
drizzleRows: { select: [[ENABLED_ROW]] },
|
|
160
|
-
});
|
|
107
|
+
const svc = makeSvc({ drizzleRows: { select: [[ENABLED_ROW]] } });
|
|
161
108
|
const result = await svc.listCommands();
|
|
162
109
|
expect(result).toHaveLength(1);
|
|
163
110
|
expect(result[0]?.key).toBe('gift');
|
|
164
111
|
});
|
|
165
112
|
it('returns all commands when includeDisabled is true', async () => {
|
|
166
|
-
|
|
167
|
-
const svc = makeSvc({
|
|
168
|
-
drizzleRows: { select: [[ENABLED_ROW, DISABLED_ROW]] },
|
|
169
|
-
});
|
|
113
|
+
const svc = makeSvc({ drizzleRows: { select: [[ENABLED_ROW, DISABLED_ROW]] } });
|
|
170
114
|
const result = await svc.listCommands(true);
|
|
171
115
|
expect(result).toHaveLength(2);
|
|
172
116
|
});
|
|
@@ -189,700 +133,228 @@ describe('ChatCommandsService.searchMentions', () => {
|
|
|
189
133
|
});
|
|
190
134
|
it('excludes ids the viewer has blocked or ignored', async () => {
|
|
191
135
|
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
|
-
});
|
|
136
|
+
const blockWriter = makeBlockWriter([ACTOR_ID]);
|
|
197
137
|
const svc = makeSvc({ directory, blockWriter });
|
|
198
138
|
const result = await svc.searchMentions('bo', 5, CLAIMER_ID);
|
|
199
139
|
expect(blockWriter.getExcludedUserIds).toHaveBeenCalledWith(CLAIMER_ID);
|
|
200
140
|
expect(result).toEqual([]);
|
|
201
141
|
});
|
|
202
142
|
});
|
|
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();
|
|
143
|
+
describe('ChatCommandsService.postGift (delegates to GIFT_COMMANDS)', () => {
|
|
144
|
+
it('returns the message from the port on success', async () => {
|
|
145
|
+
const giftCommands = makeGiftCommands();
|
|
146
|
+
const svc = makeSvc({ giftCommands });
|
|
147
|
+
const result = await svc.postGift({ amount: '10.00000000', roomId: ROOM_ID, idempotencyKey: IDEMPOTENCY_KEY }, ACTOR_ID);
|
|
148
|
+
expect(giftCommands.sendGift).toHaveBeenCalledWith({ amount: '10.00000000', roomId: ROOM_ID, idempotencyKey: IDEMPOTENCY_KEY }, ACTOR_ID);
|
|
149
|
+
expect(result).toEqual(SYSTEM_MSG);
|
|
272
150
|
});
|
|
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),
|
|
151
|
+
it('maps a "disabled" port result to CommandDisabledError', async () => {
|
|
152
|
+
const giftCommands = makeGiftCommands({
|
|
153
|
+
sendGift: vi
|
|
154
|
+
.fn()
|
|
155
|
+
.mockResolvedValue({ ok: false, reason: 'disabled' }),
|
|
295
156
|
});
|
|
296
|
-
|
|
157
|
+
const svc = makeSvc({ giftCommands });
|
|
158
|
+
await expect(svc.postGift({ amount: '10.00000000', roomId: ROOM_ID, idempotencyKey: IDEMPOTENCY_KEY }, ACTOR_ID)).rejects.toThrow(CommandDisabledError);
|
|
297
159
|
});
|
|
298
|
-
it('
|
|
299
|
-
const
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
// First returning: idempotency guard insert; second: chatGift insert.
|
|
304
|
-
returning: [[{ ...GIFT_ROW }]],
|
|
305
|
-
},
|
|
306
|
-
writer,
|
|
160
|
+
it('maps an "insufficient_balance" port result to InsufficientBalanceError', async () => {
|
|
161
|
+
const giftCommands = makeGiftCommands({
|
|
162
|
+
sendGift: vi
|
|
163
|
+
.fn()
|
|
164
|
+
.mockResolvedValue({ ok: false, reason: 'insufficient_balance' }),
|
|
307
165
|
});
|
|
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);
|
|
166
|
+
const svc = makeSvc({ giftCommands });
|
|
167
|
+
await expect(svc.postGift({ amount: '10.00000000', roomId: ROOM_ID, idempotencyKey: IDEMPOTENCY_KEY }, ACTOR_ID)).rejects.toThrow(InsufficientBalanceError);
|
|
319
168
|
});
|
|
320
|
-
it('
|
|
321
|
-
const
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
169
|
+
it('maps a "room_not_member" port result to ChatRoomNotMemberError', async () => {
|
|
170
|
+
const giftCommands = makeGiftCommands({
|
|
171
|
+
sendGift: vi
|
|
172
|
+
.fn()
|
|
173
|
+
.mockResolvedValue({ ok: false, reason: 'room_not_member' }),
|
|
325
174
|
});
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
amount: '1.00000000',
|
|
329
|
-
roomId: ROOM_ID,
|
|
330
|
-
idempotencyKey: IDEMPOTENCY_KEY,
|
|
331
|
-
}, ACTOR_ID)).rejects.toThrow(BelowMinimumError);
|
|
175
|
+
const svc = makeSvc({ giftCommands });
|
|
176
|
+
await expect(svc.postGift({ amount: '10.00000000', roomId: ROOM_ID, idempotencyKey: IDEMPOTENCY_KEY }, ACTOR_ID)).rejects.toThrow(ChatRoomNotMemberError);
|
|
332
177
|
});
|
|
333
178
|
});
|
|
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
|
-
});
|
|
179
|
+
describe('ChatCommandsService.claimGift (delegates to GIFT_COMMANDS)', () => {
|
|
180
|
+
it('returns the claim info from the port on success', async () => {
|
|
181
|
+
const giftCommands = makeGiftCommands();
|
|
182
|
+
const svc = makeSvc({ giftCommands });
|
|
446
183
|
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' }));
|
|
184
|
+
expect(giftCommands.claimGift).toHaveBeenCalledWith(GIFT_ID, CLAIMER_ID);
|
|
449
185
|
expect(result.claimedBy).toBe(CLAIMER_ID);
|
|
450
186
|
expect(result.claimedByUsername).toBe('alice');
|
|
451
|
-
expect(result.claimedAt).toEqual(expect.any(String));
|
|
452
187
|
});
|
|
453
|
-
it('
|
|
454
|
-
const
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
},
|
|
188
|
+
it('maps a "gift_not_found" port result to GiftNotFoundError', async () => {
|
|
189
|
+
const giftCommands = makeGiftCommands({
|
|
190
|
+
claimGift: vi
|
|
191
|
+
.fn()
|
|
192
|
+
.mockResolvedValue({ ok: false, reason: 'gift_not_found' }),
|
|
459
193
|
});
|
|
460
|
-
|
|
194
|
+
const svc = makeSvc({ giftCommands });
|
|
195
|
+
await expect(svc.claimGift(GIFT_ID, CLAIMER_ID)).rejects.toThrow(GiftNotFoundError);
|
|
461
196
|
});
|
|
462
|
-
it('
|
|
463
|
-
const
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
},
|
|
197
|
+
it('maps an "already_claimed" port result to GiftAlreadyClaimedError', async () => {
|
|
198
|
+
const giftCommands = makeGiftCommands({
|
|
199
|
+
claimGift: vi
|
|
200
|
+
.fn()
|
|
201
|
+
.mockResolvedValue({ ok: false, reason: 'already_claimed' }),
|
|
468
202
|
});
|
|
203
|
+
const svc = makeSvc({ giftCommands });
|
|
469
204
|
await expect(svc.claimGift(GIFT_ID, CLAIMER_ID)).rejects.toThrow(GiftAlreadyClaimedError);
|
|
470
205
|
});
|
|
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]] },
|
|
206
|
+
it('maps a "room_not_member" port result to ChatRoomNotMemberError using the port\'s roomId, not the giftId', async () => {
|
|
207
|
+
const giftCommands = makeGiftCommands({
|
|
208
|
+
claimGift: vi.fn().mockResolvedValue({
|
|
209
|
+
ok: false,
|
|
210
|
+
reason: 'room_not_member',
|
|
211
|
+
roomId: ROOM_ID,
|
|
212
|
+
}),
|
|
486
213
|
});
|
|
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
|
-
}));
|
|
214
|
+
const svc = makeSvc({ giftCommands });
|
|
215
|
+
await expect(svc.claimGift(GIFT_ID, CLAIMER_ID)).rejects.toThrow(new ChatRoomNotMemberError(ROOM_ID));
|
|
503
216
|
});
|
|
504
217
|
});
|
|
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,
|
|
218
|
+
describe('ChatCommandsService.getGift (delegates to GIFT_COMMANDS)', () => {
|
|
219
|
+
it('returns the gift state from the port on success', async () => {
|
|
220
|
+
const giftCommands = makeGiftCommands();
|
|
221
|
+
const svc = makeSvc({ giftCommands });
|
|
222
|
+
const result = await svc.getGift(GIFT_ID, CLAIMER_ID);
|
|
223
|
+
expect(giftCommands.getGift).toHaveBeenCalledWith(GIFT_ID, CLAIMER_ID);
|
|
224
|
+
expect(result).toEqual(GIFT_STATE);
|
|
225
|
+
});
|
|
226
|
+
it('maps a "gift_not_found" port result to GiftNotFoundError', async () => {
|
|
227
|
+
const giftCommands = makeGiftCommands({
|
|
228
|
+
getGift: vi
|
|
229
|
+
.fn()
|
|
230
|
+
.mockResolvedValue({ ok: false, reason: 'gift_not_found' }),
|
|
231
|
+
});
|
|
232
|
+
const svc = makeSvc({ giftCommands });
|
|
233
|
+
await expect(svc.getGift(GIFT_ID, CLAIMER_ID)).rejects.toThrow(GiftNotFoundError);
|
|
234
|
+
});
|
|
235
|
+
it('maps a "room_not_member" port result to ChatRoomNotMemberError using the port\'s roomId', async () => {
|
|
236
|
+
const giftCommands = makeGiftCommands({
|
|
237
|
+
getGift: vi.fn().mockResolvedValue({
|
|
238
|
+
ok: false,
|
|
239
|
+
reason: 'room_not_member',
|
|
532
240
|
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
241
|
}),
|
|
544
242
|
});
|
|
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();
|
|
243
|
+
const svc = makeSvc({ giftCommands });
|
|
244
|
+
await expect(svc.getGift(GIFT_ID, CLAIMER_ID)).rejects.toThrow(new ChatRoomNotMemberError(ROOM_ID));
|
|
553
245
|
});
|
|
554
|
-
|
|
246
|
+
});
|
|
247
|
+
describe('ChatCommandsService.postRain (resolves presence, delegates to RAIN_COMMANDS)', () => {
|
|
248
|
+
it('resolves online user ids via CHAT_REALTIME_TRANSPORT and passes them to the port', async () => {
|
|
555
249
|
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',
|
|
250
|
+
const transport = makeTransport([ACTOR_ID, CLAIMER_ID, RECIPIENT_2]);
|
|
251
|
+
const rainCommands = makeRainCommands();
|
|
252
|
+
const svc = makeSvc({ transport, rainCommands });
|
|
253
|
+
const result = await svc.postRain({
|
|
568
254
|
amount: '10.00000000',
|
|
569
255
|
recipientCount: 2,
|
|
570
256
|
roomId: ROOM_ID,
|
|
571
257
|
idempotencyKey: IDEMPOTENCY_KEY,
|
|
572
258
|
}, 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',
|
|
259
|
+
expect(transport.getOnlineUserIds).toHaveBeenCalledWith(`chat:room:${ROOM_ID}`);
|
|
260
|
+
expect(rainCommands.sendRain).toHaveBeenCalledWith({
|
|
769
261
|
amount: '10.00000000',
|
|
262
|
+
recipientCount: 2,
|
|
770
263
|
roomId: ROOM_ID,
|
|
771
264
|
idempotencyKey: IDEMPOTENCY_KEY,
|
|
265
|
+
onlineUserIds: [ACTOR_ID, CLAIMER_ID, RECIPIENT_2],
|
|
772
266
|
}, 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);
|
|
267
|
+
expect(result).toEqual(SYSTEM_MSG);
|
|
785
268
|
});
|
|
786
|
-
it('
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
269
|
+
it('maps a "no_online_users" port result to NoOnlineUsersError', async () => {
|
|
270
|
+
const rainCommands = makeRainCommands({
|
|
271
|
+
sendRain: vi
|
|
272
|
+
.fn()
|
|
273
|
+
.mockResolvedValue({ ok: false, reason: 'no_online_users' }),
|
|
790
274
|
});
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
targetUsername: 'bob',
|
|
275
|
+
const svc = makeSvc({ rainCommands, transport: makeTransport([ACTOR_ID]) });
|
|
276
|
+
await expect(svc.postRain({
|
|
794
277
|
amount: '10.00000000',
|
|
278
|
+
recipientCount: 2,
|
|
795
279
|
roomId: ROOM_ID,
|
|
796
280
|
idempotencyKey: IDEMPOTENCY_KEY,
|
|
797
|
-
}, ACTOR_ID)).rejects.toThrow(
|
|
281
|
+
}, ACTOR_ID)).rejects.toThrow(NoOnlineUsersError);
|
|
798
282
|
});
|
|
799
|
-
it('
|
|
800
|
-
const
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
283
|
+
it('maps a "disabled" port result to CommandDisabledError', async () => {
|
|
284
|
+
const rainCommands = makeRainCommands({
|
|
285
|
+
sendRain: vi
|
|
286
|
+
.fn()
|
|
287
|
+
.mockResolvedValue({ ok: false, reason: 'disabled' }),
|
|
804
288
|
});
|
|
805
|
-
const svc = makeSvc({
|
|
806
|
-
|
|
807
|
-
directory,
|
|
808
|
-
});
|
|
809
|
-
await expect(svc.executeCommand({
|
|
810
|
-
type: 'donate',
|
|
811
|
-
targetUsername: 'ghost',
|
|
289
|
+
const svc = makeSvc({ rainCommands });
|
|
290
|
+
await expect(svc.postRain({
|
|
812
291
|
amount: '10.00000000',
|
|
292
|
+
recipientCount: 2,
|
|
813
293
|
roomId: ROOM_ID,
|
|
814
294
|
idempotencyKey: IDEMPOTENCY_KEY,
|
|
815
|
-
}, ACTOR_ID)).rejects.toThrow(
|
|
295
|
+
}, ACTOR_ID)).rejects.toThrow(CommandDisabledError);
|
|
816
296
|
});
|
|
817
|
-
it('
|
|
818
|
-
const
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
},
|
|
823
|
-
wallet: makeWallet(false),
|
|
824
|
-
directory: makeRecipientDirectory(),
|
|
297
|
+
it('maps a "room_not_member" port result to ChatRoomNotMemberError', async () => {
|
|
298
|
+
const rainCommands = makeRainCommands({
|
|
299
|
+
sendRain: vi
|
|
300
|
+
.fn()
|
|
301
|
+
.mockResolvedValue({ ok: false, reason: 'room_not_member' }),
|
|
825
302
|
});
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
targetUsername: 'alice',
|
|
303
|
+
const svc = makeSvc({ rainCommands });
|
|
304
|
+
await expect(svc.postRain({
|
|
829
305
|
amount: '10.00000000',
|
|
306
|
+
recipientCount: 2,
|
|
830
307
|
roomId: ROOM_ID,
|
|
831
308
|
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);
|
|
309
|
+
}, ACTOR_ID)).rejects.toThrow(ChatRoomNotMemberError);
|
|
848
310
|
});
|
|
849
311
|
});
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
describe('ChatCommandsService.handleBlockAction', () => {
|
|
853
|
-
it('dispatches "block" to blockWriter.blockUser, not ignoreUser', async () => {
|
|
854
|
-
const blockWriter = makeBlockWriter();
|
|
312
|
+
describe('ChatCommandsService.adminListCommands', () => {
|
|
313
|
+
it('returns disabled commands too, unlike listCommands', async () => {
|
|
855
314
|
const svc = makeSvc({
|
|
856
|
-
drizzleRows: { select: [[
|
|
857
|
-
directory: makeRecipientDirectory(),
|
|
858
|
-
blockWriter,
|
|
315
|
+
drizzleRows: { select: [[ENABLED_ROW, DISABLED_ROW], [{ n: 2 }]] },
|
|
859
316
|
});
|
|
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,
|
|
317
|
+
const result = await svc.adminListCommands({
|
|
318
|
+
page: 1,
|
|
319
|
+
limit: 10,
|
|
320
|
+
sortBy: 'key',
|
|
321
|
+
sortOrder: 'asc',
|
|
870
322
|
});
|
|
871
|
-
|
|
872
|
-
expect(
|
|
873
|
-
expect(
|
|
323
|
+
expect(result.items).toHaveLength(2);
|
|
324
|
+
expect(result.total).toBe(2);
|
|
325
|
+
expect(result.items.map((c) => c.enabled)).toEqual([true, false]);
|
|
874
326
|
});
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
327
|
+
});
|
|
328
|
+
describe('ChatCommandsService.adminUpdateCommand', () => {
|
|
329
|
+
it('creates a new config row when none exists', async () => {
|
|
330
|
+
const row = { ...ENABLED_ROW, key: 'donate', label: 'Donate' };
|
|
331
|
+
const svc = makeSvc({ drizzleRows: { returning: [[row]] } });
|
|
332
|
+
const result = await svc.adminUpdateCommand({ key: 'donate', enabled: true }, ACTOR_ID);
|
|
333
|
+
expect(result).toMatchObject({ key: 'donate', label: 'Donate', enabled: true });
|
|
334
|
+
});
|
|
335
|
+
it('updates an existing config row', async () => {
|
|
336
|
+
const row = { ...ENABLED_ROW, enabled: false, config: { maxAmount: '100.00000000' } };
|
|
337
|
+
const svc = makeSvc({ drizzleRows: { returning: [[row]] } });
|
|
338
|
+
const result = await svc.adminUpdateCommand({ key: 'gift', enabled: false, config: { maxAmount: '100.00000000' } }, ACTOR_ID);
|
|
339
|
+
expect(result).toMatchObject({ key: 'gift', enabled: false });
|
|
340
|
+
});
|
|
341
|
+
it('records an audit entry describing the change', async () => {
|
|
342
|
+
const audit = makeAudit();
|
|
343
|
+
const svc = makeSvc({ audit, drizzleRows: { returning: [[ENABLED_ROW]] } });
|
|
344
|
+
await svc.adminUpdateCommand({ key: 'gift', enabled: true, config: { maxAmount: '50.00000000' } }, ACTOR_ID);
|
|
345
|
+
expect(audit.record).toHaveBeenCalledWith({
|
|
346
|
+
actorId: ACTOR_ID,
|
|
347
|
+
actorType: 'admin',
|
|
348
|
+
action: 'chat.command.updated',
|
|
349
|
+
resourceType: 'chat_command',
|
|
350
|
+
resourceId: 'gift',
|
|
351
|
+
before: null,
|
|
352
|
+
after: { enabled: true, config: { maxAmount: '50.00000000' } },
|
|
878
353
|
});
|
|
879
|
-
await expect(svc.executeCommand({ type: 'block', targetUsername: 'bob', roomId: ROOM_ID }, ACTOR_ID)).rejects.toThrow(SelfModerationActionError);
|
|
880
354
|
});
|
|
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);
|
|
355
|
+
it('throws CommandDisabledError when the insert/update returns no row', async () => {
|
|
356
|
+
const svc = makeSvc({ drizzleRows: { returning: [[]] } });
|
|
357
|
+
await expect(svc.adminUpdateCommand({ key: 'gift', enabled: true }, ACTOR_ID)).rejects.toThrow(CommandDisabledError);
|
|
886
358
|
});
|
|
887
359
|
});
|
|
888
360
|
//# sourceMappingURL=chat-commands.service.test.js.map
|