@takaro/modules 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (105) hide show
  1. package/dist/BuiltinModule.d.ts +3 -0
  2. package/dist/BuiltinModule.d.ts.map +1 -1
  3. package/dist/BuiltinModule.js +17 -0
  4. package/dist/BuiltinModule.js.map +1 -1
  5. package/dist/dto/index.d.ts +1 -0
  6. package/dist/dto/index.d.ts.map +1 -1
  7. package/dist/dto/takaroEvents.d.ts +7 -0
  8. package/dist/dto/takaroEvents.d.ts.map +1 -1
  9. package/dist/dto/takaroEvents.js +23 -1
  10. package/dist/dto/takaroEvents.js.map +1 -1
  11. package/dist/modules/chatBridge/index.d.ts.map +1 -1
  12. package/dist/modules/chatBridge/index.js +2 -0
  13. package/dist/modules/chatBridge/index.js.map +1 -1
  14. package/dist/modules/dailyRewards/commands/daily.js +1 -4
  15. package/dist/modules/dailyRewards/commands/daily.js.map +1 -1
  16. package/dist/modules/dailyRewards/index.d.ts.map +1 -1
  17. package/dist/modules/dailyRewards/index.js +4 -1
  18. package/dist/modules/dailyRewards/index.js.map +1 -1
  19. package/dist/modules/economyUtils/commands/confirmTransfer.js +4 -4
  20. package/dist/modules/economyUtils/commands/confirmTransfer.js.map +1 -1
  21. package/dist/modules/economyUtils/commands/grantCurrency.js +1 -4
  22. package/dist/modules/economyUtils/commands/grantCurrency.js.map +1 -1
  23. package/dist/modules/economyUtils/commands/revokeCurrency.js +1 -4
  24. package/dist/modules/economyUtils/commands/revokeCurrency.js.map +1 -1
  25. package/dist/modules/economyUtils/index.d.ts.map +1 -1
  26. package/dist/modules/economyUtils/index.js +5 -1
  27. package/dist/modules/economyUtils/index.js.map +1 -1
  28. package/dist/modules/geoBlock/index.d.ts.map +1 -1
  29. package/dist/modules/geoBlock/index.js +2 -0
  30. package/dist/modules/geoBlock/index.js.map +1 -1
  31. package/dist/modules/gimme/index.d.ts.map +1 -1
  32. package/dist/modules/gimme/index.js +2 -0
  33. package/dist/modules/gimme/index.js.map +1 -1
  34. package/dist/modules/highPingKicker/index.d.ts.map +1 -1
  35. package/dist/modules/highPingKicker/index.js +2 -0
  36. package/dist/modules/highPingKicker/index.js.map +1 -1
  37. package/dist/modules/lottery/commands/buyTicket.js +1 -4
  38. package/dist/modules/lottery/commands/buyTicket.js.map +1 -1
  39. package/dist/modules/lottery/commands/viewTickets.js +5 -8
  40. package/dist/modules/lottery/commands/viewTickets.js.map +1 -1
  41. package/dist/modules/lottery/index.d.ts.map +1 -1
  42. package/dist/modules/lottery/index.js +5 -1
  43. package/dist/modules/lottery/index.js.map +1 -1
  44. package/dist/modules/playerOnboarding/index.d.ts.map +1 -1
  45. package/dist/modules/playerOnboarding/index.js +2 -0
  46. package/dist/modules/playerOnboarding/index.js.map +1 -1
  47. package/dist/modules/serverMessages/index.d.ts.map +1 -1
  48. package/dist/modules/serverMessages/index.js +2 -0
  49. package/dist/modules/serverMessages/index.js.map +1 -1
  50. package/dist/modules/teleports/commands/deletewaypoint.js +1 -4
  51. package/dist/modules/teleports/commands/deletewaypoint.js.map +1 -1
  52. package/dist/modules/teleports/commands/setpublic.js +0 -3
  53. package/dist/modules/teleports/commands/setpublic.js.map +1 -1
  54. package/dist/modules/teleports/commands/settp.js +1 -4
  55. package/dist/modules/teleports/commands/settp.js.map +1 -1
  56. package/dist/modules/teleports/commands/setwaypoint.js +1 -4
  57. package/dist/modules/teleports/commands/setwaypoint.js.map +1 -1
  58. package/dist/modules/teleports/commands/teleport.js +1 -4
  59. package/dist/modules/teleports/commands/teleport.js.map +1 -1
  60. package/dist/modules/teleports/index.d.ts.map +1 -1
  61. package/dist/modules/teleports/index.js +9 -1
  62. package/dist/modules/teleports/index.js.map +1 -1
  63. package/dist/modules/timedShutdown/index.d.ts.map +1 -1
  64. package/dist/modules/timedShutdown/index.js +2 -0
  65. package/dist/modules/timedShutdown/index.js.map +1 -1
  66. package/dist/modules/utils/commands/help.js +60 -6
  67. package/dist/modules/utils/commands/help.js.map +1 -1
  68. package/dist/modules/utils/index.d.ts.map +1 -1
  69. package/dist/modules/utils/index.js +11 -2
  70. package/dist/modules/utils/index.js.map +1 -1
  71. package/dist/modules.json +112 -18
  72. package/package.json +1 -1
  73. package/src/BuiltinModule.ts +11 -0
  74. package/src/__tests__/economy/economyUtils.integration.test.ts +6 -2
  75. package/src/__tests__/help.integration.test.ts +230 -62
  76. package/src/__tests__/lottery.integration.test.ts +5 -1
  77. package/src/__tests__/modulePermission.integration.test.ts +197 -11
  78. package/src/__tests__/roleExpiry.integration.test.ts +6 -2
  79. package/src/__tests__/teleports/publicteleports.integration.test.ts +1 -1
  80. package/src/__tests__/teleports/waypoints.integration.test.ts +6 -2
  81. package/src/dto/takaroEvents.ts +16 -0
  82. package/src/modules/chatBridge/index.ts +2 -0
  83. package/src/modules/dailyRewards/commands/daily.js +1 -5
  84. package/src/modules/dailyRewards/index.ts +4 -1
  85. package/src/modules/economyUtils/commands/confirmTransfer.js +6 -6
  86. package/src/modules/economyUtils/commands/grantCurrency.js +1 -5
  87. package/src/modules/economyUtils/commands/revokeCurrency.js +1 -5
  88. package/src/modules/economyUtils/index.ts +5 -1
  89. package/src/modules/geoBlock/index.ts +2 -0
  90. package/src/modules/gimme/index.ts +2 -0
  91. package/src/modules/highPingKicker/index.ts +2 -0
  92. package/src/modules/lottery/commands/buyTicket.js +1 -5
  93. package/src/modules/lottery/commands/viewTickets.js +5 -9
  94. package/src/modules/lottery/index.ts +5 -1
  95. package/src/modules/playerOnboarding/index.ts +2 -0
  96. package/src/modules/serverMessages/index.ts +2 -0
  97. package/src/modules/teleports/commands/deletewaypoint.js +1 -5
  98. package/src/modules/teleports/commands/setpublic.js +1 -5
  99. package/src/modules/teleports/commands/settp.js +1 -6
  100. package/src/modules/teleports/commands/setwaypoint.js +1 -5
  101. package/src/modules/teleports/commands/teleport.js +1 -5
  102. package/src/modules/teleports/index.ts +9 -1
  103. package/src/modules/timedShutdown/index.ts +2 -0
  104. package/src/modules/utils/commands/help.js +70 -8
  105. package/src/modules/utils/index.ts +11 -2
@@ -1,140 +1,308 @@
1
- import {
2
- IntegrationTest,
3
- expect,
4
- IModuleTestsSetupData,
5
- modulesTestSetup,
6
- chatMessageSorter,
7
- EventsAwaiter,
8
- } from '@takaro/test';
9
- import { GameEvents } from '../dto/gameEvents.js';
1
+ import { IntegrationTest, expect, IModuleTestsSetupData, modulesTestSetup, EventsAwaiter } from '@takaro/test';
2
+ import { GameEvents } from '../dto/index.js';
10
3
  import { describe } from 'node:test';
11
4
 
12
- const group = 'Help command';
5
+ const group = 'help command suite';
13
6
 
14
7
  const tests = [
15
8
  new IntegrationTest<IModuleTestsSetupData>({
16
9
  group,
17
10
  snapshot: false,
18
11
  setup: modulesTestSetup,
19
- name: 'Help command responds with a list of installed commands',
12
+ name: 'Shows all available commands when no argument provided',
20
13
  test: async function () {
21
14
  await this.client.module.moduleInstallationsControllerInstallModule({
22
15
  gameServerId: this.setupData.gameserver.id,
23
16
  versionId: this.setupData.utilsModule.latestVersion.id,
24
17
  });
18
+
25
19
  const events = (await new EventsAwaiter().connect(this.client)).waitForEvents(GameEvents.CHAT_MESSAGE, 3);
20
+
26
21
  await this.client.command.commandControllerTrigger(this.setupData.gameserver.id, {
27
22
  msg: '/help',
28
23
  playerId: this.setupData.players[0].id,
29
24
  });
30
25
 
31
- expect((await events).length).to.be.eq(3);
32
- const sortedEvents = (await events).sort(chatMessageSorter);
26
+ const messages = await events;
27
+ expect(messages.length).to.be.greaterThan(0);
28
+ expect(messages[0].data.meta.msg).to.include('Available commands:');
29
+ // Should show help and ping commands from utils module
30
+ expect(messages.some((m) => m.data.meta.msg.includes('help:'))).to.be.true;
31
+ expect(messages.some((m) => m.data.meta.msg.includes('ping:'))).to.be.true;
32
+ },
33
+ }),
34
+ new IntegrationTest<IModuleTestsSetupData>({
35
+ group,
36
+ snapshot: false,
37
+ setup: modulesTestSetup,
38
+ name: 'Shows specific command help when command name provided',
39
+ test: async function () {
40
+ await this.client.module.moduleInstallationsControllerInstallModule({
41
+ gameServerId: this.setupData.gameserver.id,
42
+ versionId: this.setupData.utilsModule.latestVersion.id,
43
+ });
44
+
45
+ const events = (await new EventsAwaiter().connect(this.client)).waitForEvents(GameEvents.CHAT_MESSAGE);
33
46
 
34
- expect(sortedEvents[0].data.meta.msg).to.be.eq('Available commands:');
35
- expect(sortedEvents[1].data.meta.msg).to.be.eq(
36
- 'help: The text you are reading right now, displays information about commands.',
37
- );
38
- expect(sortedEvents[2].data.meta.msg).to.be.eq(
39
- 'ping: Replies with pong, useful for testing if the connection works.',
40
- );
47
+ await this.client.command.commandControllerTrigger(this.setupData.gameserver.id, {
48
+ msg: '/help ping',
49
+ playerId: this.setupData.players[0].id,
50
+ });
51
+
52
+ const messages = await events;
53
+ expect(messages.length).to.be.eq(1);
54
+ expect(messages[0].data.meta.msg).to.include('ping:');
55
+ expect(messages[0].data.meta.msg).to.include('pong');
41
56
  },
42
57
  }),
43
58
  new IntegrationTest<IModuleTestsSetupData>({
44
59
  group,
45
60
  snapshot: false,
46
61
  setup: modulesTestSetup,
47
- name: 'Help command responds with a list of installed commands, picking up commands from multiple modules',
62
+ name: 'Search returns commands with matching names',
48
63
  test: async function () {
49
64
  await this.client.module.moduleInstallationsControllerInstallModule({
50
65
  gameServerId: this.setupData.gameserver.id,
51
66
  versionId: this.setupData.utilsModule.latestVersion.id,
52
67
  });
68
+
69
+ const events = (await new EventsAwaiter().connect(this.client)).waitForEvents(GameEvents.CHAT_MESSAGE, 2);
70
+
71
+ await this.client.command.commandControllerTrigger(this.setupData.gameserver.id, {
72
+ msg: '/help search ping',
73
+ playerId: this.setupData.players[0].id,
74
+ });
75
+
76
+ const messages = await events;
77
+ expect(messages.length).to.be.greaterThan(0);
78
+ expect(messages[0].data.meta.msg).to.include('Commands matching "ping":');
79
+ expect(messages.some((m) => m.data.meta.msg.includes('ping:'))).to.be.true;
80
+ },
81
+ }),
82
+ new IntegrationTest<IModuleTestsSetupData>({
83
+ group,
84
+ snapshot: false,
85
+ setup: modulesTestSetup,
86
+ name: 'Search returns commands with matching help text',
87
+ test: async function () {
53
88
  await this.client.module.moduleInstallationsControllerInstallModule({
54
89
  gameServerId: this.setupData.gameserver.id,
55
- versionId: this.setupData.teleportsModule.latestVersion.id,
90
+ versionId: this.setupData.utilsModule.latestVersion.id,
56
91
  });
57
- const events = (await new EventsAwaiter().connect(this.client)).waitForEvents(GameEvents.CHAT_MESSAGE, 13);
92
+
93
+ const events = (await new EventsAwaiter().connect(this.client)).waitForEvents(GameEvents.CHAT_MESSAGE, 2);
94
+
58
95
  await this.client.command.commandControllerTrigger(this.setupData.gameserver.id, {
59
- msg: '/help',
96
+ msg: '/help search pong',
60
97
  playerId: this.setupData.players[0].id,
61
98
  });
62
99
 
63
- const sortedEvents = (await events).sort(chatMessageSorter);
100
+ const messages = await events;
101
+ expect(messages.length).to.be.greaterThan(0);
102
+ expect(messages[0].data.meta.msg).to.include('Commands matching "pong":');
103
+ expect(messages.some((m) => m.data.meta.msg.includes('ping:'))).to.be.true;
104
+ },
105
+ }),
106
+ new IntegrationTest<IModuleTestsSetupData>({
107
+ group,
108
+ snapshot: false,
109
+ setup: modulesTestSetup,
110
+ name: 'Search is case-insensitive',
111
+ test: async function () {
112
+ await this.client.module.moduleInstallationsControllerInstallModule({
113
+ gameServerId: this.setupData.gameserver.id,
114
+ versionId: this.setupData.utilsModule.latestVersion.id,
115
+ });
64
116
 
65
- expect(sortedEvents[0].data.meta.msg).to.be.eq('Available commands:');
66
- expect(sortedEvents[1].data.meta.msg).to.be.eq('deletetp: Deletes a location.');
67
- expect(sortedEvents[2].data.meta.msg).to.be.eq('deletewaypoint: Deletes a waypoint.');
68
- expect(sortedEvents[3].data.meta.msg).to.be.eq(
69
- 'help: The text you are reading right now, displays information about commands.',
70
- );
71
- expect(sortedEvents[4].data.meta.msg).to.be.eq('listwaypoints: Lists all waypoints.');
72
- expect(sortedEvents[5].data.meta.msg).to.be.eq(
73
- 'ping: Replies with pong, useful for testing if the connection works.',
74
- );
75
- expect(sortedEvents[6].data.meta.msg).to.be.eq(
76
- 'setprivate: Sets a teleport to be private, only the teleport owner can teleport to it.',
77
- );
78
- expect(sortedEvents[7].data.meta.msg).to.be.eq(
79
- 'setpublic: Sets a teleport to be public, allowing other players to teleport to it.',
80
- );
81
- expect(sortedEvents[8].data.meta.msg).to.be.eq('settp: Sets a location to teleport to.');
82
- expect(sortedEvents[9].data.meta.msg).to.be.eq('setwaypoint: Creates a new waypoint.');
83
- expect(sortedEvents[10].data.meta.msg).to.be.eq('teleport: Teleports to one of your set locations.');
84
- expect(sortedEvents[11].data.meta.msg).to.be.eq(
85
- 'teleportwaypoint: Placeholder command, this will not be used directly. The module will install aliases for this command corresponding to the waypoint names.',
86
- );
87
- expect(sortedEvents[12].data.meta.msg).to.be.eq('tplist: Lists all your set locations.');
117
+ const events = (await new EventsAwaiter().connect(this.client)).waitForEvents(GameEvents.CHAT_MESSAGE, 2);
118
+
119
+ await this.client.command.commandControllerTrigger(this.setupData.gameserver.id, {
120
+ msg: '/help search PING',
121
+ playerId: this.setupData.players[0].id,
122
+ });
123
+
124
+ const messages = await events;
125
+ expect(messages.length).to.be.greaterThan(0);
126
+ expect(messages[0].data.meta.msg).to.include('Commands matching "PING":');
127
+ expect(messages.some((m) => m.data.meta.msg.includes('ping:'))).to.be.true;
88
128
  },
89
129
  }),
90
130
  new IntegrationTest<IModuleTestsSetupData>({
91
131
  group,
92
132
  snapshot: false,
93
133
  setup: modulesTestSetup,
94
- name: 'Help command responds with detailed info about a specific command',
134
+ name: 'Search supports partial matches',
95
135
  test: async function () {
96
136
  await this.client.module.moduleInstallationsControllerInstallModule({
97
137
  gameServerId: this.setupData.gameserver.id,
98
138
  versionId: this.setupData.utilsModule.latestVersion.id,
99
139
  });
100
140
 
101
- const events = (await new EventsAwaiter().connect(this.client)).waitForEvents(GameEvents.CHAT_MESSAGE, 1);
141
+ const events = (await new EventsAwaiter().connect(this.client)).waitForEvents(GameEvents.CHAT_MESSAGE, 2);
102
142
 
103
143
  await this.client.command.commandControllerTrigger(this.setupData.gameserver.id, {
104
- msg: '/help ping',
144
+ msg: '/help search hel',
105
145
  playerId: this.setupData.players[0].id,
106
146
  });
107
147
 
108
- expect((await events).length).to.be.eq(1);
109
- const sortedEvents = (await events).sort(chatMessageSorter);
148
+ const messages = await events;
149
+ expect(messages.length).to.be.greaterThan(0);
150
+ expect(messages[0].data.meta.msg).to.include('Commands matching "hel":');
151
+ expect(messages.some((m) => m.data.meta.msg.includes('help:'))).to.be.true;
152
+ },
153
+ }),
154
+ new IntegrationTest<IModuleTestsSetupData>({
155
+ group,
156
+ snapshot: false,
157
+ setup: modulesTestSetup,
158
+ name: 'Search returns appropriate message when no matches found',
159
+ test: async function () {
160
+ await this.client.module.moduleInstallationsControllerInstallModule({
161
+ gameServerId: this.setupData.gameserver.id,
162
+ versionId: this.setupData.utilsModule.latestVersion.id,
163
+ });
110
164
 
111
- expect(sortedEvents[0].data.meta.msg).to.be.eq(
112
- 'ping: Replies with pong, useful for testing if the connection works.',
113
- );
165
+ const events = (await new EventsAwaiter().connect(this.client)).waitForEvents(GameEvents.CHAT_MESSAGE);
166
+
167
+ await this.client.command.commandControllerTrigger(this.setupData.gameserver.id, {
168
+ msg: '/help search nonexistentcommand',
169
+ playerId: this.setupData.players[0].id,
170
+ });
171
+
172
+ const messages = await events;
173
+ expect(messages.length).to.be.eq(1);
174
+ expect(messages[0].data.meta.msg).to.include('No commands found matching "nonexistentcommand"');
175
+ },
176
+ }),
177
+ new IntegrationTest<IModuleTestsSetupData>({
178
+ group,
179
+ snapshot: false,
180
+ setup: modulesTestSetup,
181
+ name: 'Search works across multiple modules',
182
+ test: async function () {
183
+ // Install utils module
184
+ await this.client.module.moduleInstallationsControllerInstallModule({
185
+ gameServerId: this.setupData.gameserver.id,
186
+ versionId: this.setupData.utilsModule.latestVersion.id,
187
+ });
188
+
189
+ // Install another module (gimme) to have more commands
190
+ await this.client.module.moduleInstallationsControllerInstallModule({
191
+ gameServerId: this.setupData.gameserver.id,
192
+ versionId: this.setupData.gimmeModule.latestVersion.id,
193
+ userConfig: JSON.stringify({
194
+ items: [],
195
+ commands: ['say test'],
196
+ }),
197
+ });
198
+
199
+ const events = (await new EventsAwaiter().connect(this.client)).waitForEvents(GameEvents.CHAT_MESSAGE, 2);
200
+
201
+ await this.client.command.commandControllerTrigger(this.setupData.gameserver.id, {
202
+ msg: '/help search command',
203
+ playerId: this.setupData.players[0].id,
204
+ });
205
+
206
+ const messages = await events;
207
+ expect(messages.length).to.be.greaterThan(0);
208
+ expect(messages[0].data.meta.msg).to.include('Commands matching "command":');
209
+ // Should find help command which has "command" in its help text
210
+ expect(messages.some((m) => m.data.meta.msg.includes('help:'))).to.be.true;
114
211
  },
115
212
  }),
116
213
  new IntegrationTest<IModuleTestsSetupData>({
117
214
  group,
118
215
  snapshot: false,
119
216
  setup: modulesTestSetup,
120
- name: 'Help command responds with unknown command message if command does not exist',
217
+ name: 'Does not show commands from disabled modules',
121
218
  test: async function () {
219
+ // First install utils module normally so help command is available
122
220
  await this.client.module.moduleInstallationsControllerInstallModule({
123
221
  gameServerId: this.setupData.gameserver.id,
124
222
  versionId: this.setupData.utilsModule.latestVersion.id,
125
223
  });
126
224
 
127
- const events = (await new EventsAwaiter().connect(this.client)).waitForEvents(GameEvents.CHAT_MESSAGE, 1);
225
+ // Install teleports module but disabled
226
+ const teleportsInstallation = await this.client.module.moduleInstallationsControllerInstallModule({
227
+ gameServerId: this.setupData.gameserver.id,
228
+ versionId: this.setupData.teleportsModule.latestVersion.id,
229
+ });
230
+
231
+ // Get the current system config and disable the teleports module
232
+ const systemConfig = teleportsInstallation.data.data.systemConfig as any;
233
+ systemConfig.enabled = false;
234
+
235
+ // Uninstall and reinstall teleports module with disabled config
236
+ await this.client.module.moduleInstallationsControllerUninstallModule(
237
+ teleportsInstallation.data.data.moduleId,
238
+ this.setupData.gameserver.id,
239
+ );
240
+
241
+ await this.client.module.moduleInstallationsControllerInstallModule({
242
+ gameServerId: this.setupData.gameserver.id,
243
+ versionId: this.setupData.teleportsModule.latestVersion.id,
244
+ systemConfig: JSON.stringify(systemConfig),
245
+ });
246
+
247
+ const events = (await new EventsAwaiter().connect(this.client)).waitForEvents(GameEvents.CHAT_MESSAGE, 3);
128
248
 
129
249
  await this.client.command.commandControllerTrigger(this.setupData.gameserver.id, {
130
- msg: '/help foobar',
250
+ msg: '/help',
131
251
  playerId: this.setupData.players[0].id,
132
252
  });
133
253
 
134
- expect((await events).length).to.be.eq(1);
135
- expect((await events)[0].data.meta.msg).to.be.eq(
136
- 'Unknown command "foobar", use this command without arguments to see all available commands.',
254
+ const messages = await events;
255
+ expect(messages.length).to.be.greaterThan(0);
256
+ expect(messages[0].data.meta.msg).to.include('Available commands:');
257
+ // Should show commands from utils module but NOT from disabled teleports module
258
+ expect(messages.some((m) => m.data.meta.msg.includes('help:'))).to.be.true;
259
+ expect(messages.some((m) => m.data.meta.msg.includes('ping:'))).to.be.true;
260
+ // Should NOT show teleports commands
261
+ expect(messages.some((m) => m.data.meta.msg.includes('settp:'))).to.be.false;
262
+ expect(messages.some((m) => m.data.meta.msg.includes('teleport:'))).to.be.false;
263
+ },
264
+ }),
265
+ new IntegrationTest<IModuleTestsSetupData>({
266
+ group,
267
+ snapshot: false,
268
+ setup: modulesTestSetup,
269
+ name: 'Does not show individually disabled commands',
270
+ test: async function () {
271
+ // Install utils module
272
+ const moduleInstallation = await this.client.module.moduleInstallationsControllerInstallModule({
273
+ gameServerId: this.setupData.gameserver.id,
274
+ versionId: this.setupData.utilsModule.latestVersion.id,
275
+ });
276
+
277
+ // Disable only the ping command
278
+ const systemConfig = moduleInstallation.data.data.systemConfig as any;
279
+ systemConfig.commands.ping.enabled = false;
280
+
281
+ // Uninstall and reinstall with new config
282
+ await this.client.module.moduleInstallationsControllerUninstallModule(
283
+ moduleInstallation.data.data.moduleId,
284
+ this.setupData.gameserver.id,
137
285
  );
286
+
287
+ await this.client.module.moduleInstallationsControllerInstallModule({
288
+ gameServerId: this.setupData.gameserver.id,
289
+ versionId: this.setupData.utilsModule.latestVersion.id,
290
+ systemConfig: JSON.stringify(systemConfig),
291
+ });
292
+
293
+ const events = (await new EventsAwaiter().connect(this.client)).waitForEvents(GameEvents.CHAT_MESSAGE, 2);
294
+
295
+ await this.client.command.commandControllerTrigger(this.setupData.gameserver.id, {
296
+ msg: '/help',
297
+ playerId: this.setupData.players[0].id,
298
+ });
299
+
300
+ const messages = await events;
301
+ expect(messages.length).to.be.greaterThan(0);
302
+ expect(messages[0].data.meta.msg).to.include('Available commands:');
303
+ // Should show help command but NOT ping command
304
+ expect(messages.some((m) => m.data.meta.msg.includes('help:'))).to.be.true;
305
+ expect(messages.some((m) => m.data.meta.msg.includes('ping:'))).to.be.false;
138
306
  },
139
307
  }),
140
308
  ];
@@ -171,7 +171,11 @@ const tests = [
171
171
  playerId: this.setupData.players[0].id,
172
172
  });
173
173
 
174
- await waitForBuyEvents;
174
+ const buyEvents = await waitForBuyEvents;
175
+ expect(buyEvents.length).to.be.eq(1);
176
+ expect(buyEvents[0].data.meta.msg).to.be.eq(
177
+ `You have successfully bought ${wantAmount} tickets for ${wantAmount * ticketCost} Takaro coins. Good luck!`,
178
+ );
175
179
 
176
180
  const waitForViewEvent = (await new EventsAwaiter().connect(this.client)).waitForEvents(GameEvents.CHAT_MESSAGE);
177
181
 
@@ -1,5 +1,5 @@
1
1
  import { IntegrationTest, expect, IModuleTestsSetupData, modulesTestSetup, EventsAwaiter } from '@takaro/test';
2
- import { GameEvents } from '../dto/index.js';
2
+ import { IHookEventTypeEnum } from '@takaro/apiclient';
3
3
  import { describe } from 'node:test';
4
4
  import { randomUUID } from 'crypto';
5
5
  import { getMockServer } from '@takaro/mock-gameserver';
@@ -55,7 +55,10 @@ const tests = [
55
55
  });
56
56
  await this.client.player.playerControllerAssignRole(this.setupData.players[0].id, this.setupData.role.id);
57
57
 
58
- const setEvents = (await new EventsAwaiter().connect(this.client)).waitForEvents(GameEvents.CHAT_MESSAGE, 1);
58
+ const setEvents = (await new EventsAwaiter().connect(this.client)).waitForEvents(
59
+ IHookEventTypeEnum.ChatMessage,
60
+ 1,
61
+ );
59
62
  await this.client.command.commandControllerTrigger(this.setupData.gameserver.id, {
60
63
  msg: '/settp test',
61
64
  playerId: this.setupData.players[0].id,
@@ -87,14 +90,19 @@ const tests = [
87
90
  gameServerId: newGameServer.id,
88
91
  });
89
92
 
90
- const setEvents = (await new EventsAwaiter().connect(this.client)).waitForEvents(GameEvents.CHAT_MESSAGE, 1);
93
+ const setEvents = (await new EventsAwaiter().connect(this.client)).waitForEvents(
94
+ IHookEventTypeEnum.ChatMessage,
95
+ 1,
96
+ );
91
97
  await this.client.command.commandControllerTrigger(this.setupData.gameserver.id, {
92
98
  msg: '/settp test',
93
99
  playerId: this.setupData.players[0].id,
94
100
  });
95
101
 
96
102
  expect((await setEvents).length).to.be.eq(1);
97
- expect((await setEvents)[0].data.meta.msg).to.be.eq('You do not have permission to use teleports.');
103
+ expect((await setEvents)[0].data.meta.msg).to.be.eq(
104
+ "⚠️ You need the 'Teleports Use' permission to use this command. Please contact an admin if you need access.",
105
+ );
98
106
  },
99
107
  }),
100
108
  new IntegrationTest<IModuleTestsSetupData>({
@@ -117,7 +125,10 @@ const tests = [
117
125
  gameServerId: this.setupData.gameserver.id,
118
126
  });
119
127
 
120
- const setEvents = (await new EventsAwaiter().connect(this.client)).waitForEvents(GameEvents.CHAT_MESSAGE, 1);
128
+ const setEvents = (await new EventsAwaiter().connect(this.client)).waitForEvents(
129
+ IHookEventTypeEnum.ChatMessage,
130
+ 1,
131
+ );
121
132
  await this.client.command.commandControllerTrigger(this.setupData.gameserver.id, {
122
133
  msg: '/settp test',
123
134
  playerId: this.setupData.players[0].id,
@@ -148,6 +159,7 @@ const tests = [
148
159
  );
149
160
 
150
161
  const playerRoleRes = await this.client.role.roleControllerSearch({ filters: { name: ['Player'] } });
162
+
151
163
  const useTeleportsRole = await this.client.permissionCodesToInputs(['TELEPORTS_USE']);
152
164
  await this.client.role.roleControllerUpdate(playerRoleRes.data.data[0].id, {
153
165
  permissions: [
@@ -158,17 +170,22 @@ const tests = [
158
170
  ],
159
171
  });
160
172
 
161
- const setTpEvent = (await new EventsAwaiter().connect(this.client)).waitForEvents(GameEvents.CHAT_MESSAGE, 1);
173
+ const setTpEvent = (await new EventsAwaiter().connect(this.client)).waitForEvents(
174
+ IHookEventTypeEnum.ChatMessage,
175
+ 1,
176
+ );
162
177
 
163
178
  await this.client.command.commandControllerTrigger(this.setupData.gameserver.id, {
164
179
  msg: '/settp test',
165
180
  playerId: this.setupData.players[0].id,
166
181
  });
167
182
 
168
- expect((await setTpEvent).length).to.be.eq(1);
169
- expect((await setTpEvent)[0].data.meta.msg).to.be.eq('Teleport test set.');
183
+ const setTpEventResult = await setTpEvent;
184
+
185
+ expect(setTpEventResult.length).to.be.eq(1);
186
+ expect(setTpEventResult[0].data.meta.msg).to.be.eq('Teleport test set.');
170
187
 
171
- const tpEvent = (await new EventsAwaiter().connect(this.client)).waitForEvents(GameEvents.CHAT_MESSAGE, 1);
188
+ const tpEvent = (await new EventsAwaiter().connect(this.client)).waitForEvents(IHookEventTypeEnum.ChatMessage, 1);
172
189
 
173
190
  await this.client.command.commandControllerTrigger(this.setupData.gameserver.id, {
174
191
  msg: '/tp test',
@@ -182,14 +199,183 @@ const tests = [
182
199
  permissions: [],
183
200
  });
184
201
 
185
- const tpEventNoPerm = (await new EventsAwaiter().connect(this.client)).waitForEvents(GameEvents.CHAT_MESSAGE, 1);
202
+ const tpEventNoPerm = (await new EventsAwaiter().connect(this.client)).waitForEvents(
203
+ IHookEventTypeEnum.ChatMessage,
204
+ 1,
205
+ );
186
206
 
187
207
  await this.client.command.commandControllerTrigger(this.setupData.gameserver.id, {
188
208
  msg: '/tp test',
189
209
  playerId: this.setupData.players[0].id,
190
210
  });
191
211
 
192
- expect((await tpEventNoPerm)[0].data.meta.msg).to.be.eq('You do not have permission to use teleports.');
212
+ expect((await tpEventNoPerm)[0].data.meta.msg).to.include("You need the 'Teleports Use' permission");
213
+ },
214
+ }),
215
+ new IntegrationTest<IModuleTestsSetupData>({
216
+ group,
217
+ snapshot: false,
218
+ setup: async function () {
219
+ const defaultSetup = await modulesTestSetup.bind(this)();
220
+
221
+ // Remove all permissions from the default role
222
+ await this.client.role.roleControllerUpdate(defaultSetup.role.id, {
223
+ name: defaultSetup.role.name,
224
+ permissions: [],
225
+ });
226
+
227
+ return defaultSetup;
228
+ },
229
+ name: 'Command execution denied when player lacks requiredPermissions',
230
+ test: async function () {
231
+ // Install economyUtils module which has commands with requiredPermissions
232
+ const installRes = await this.client.module.moduleInstallationsControllerInstallModule({
233
+ gameServerId: this.setupData.gameserver.id,
234
+ versionId: this.setupData.economyUtilsModule.latestVersion.id,
235
+ });
236
+
237
+ // Verify installation succeeded
238
+ expect(installRes.data.data).to.exist;
239
+
240
+ // Ensure we have at least 1 player
241
+ expect(this.setupData.players.length).to.be.at.least(1);
242
+ const executingPlayer = this.setupData.players[0];
243
+
244
+ // Remove any existing roles from the player to ensure they have no special permissions
245
+ await this.client.player.playerControllerRemoveRole(executingPlayer.id, this.setupData.role.id);
246
+
247
+ // Check player's roles after removal
248
+ const playerAfter = await this.client.player.playerControllerGetOne(executingPlayer.id);
249
+
250
+ // If player still has system roles, we need to handle the Player role specially
251
+ if (playerAfter.data.data.roleAssignments && playerAfter.data.data.roleAssignments.length > 0) {
252
+ // Find the Player role
253
+ const playerRole = playerAfter.data.data.roleAssignments.find((ra) => ra.role.name === 'Player');
254
+ if (!playerRole) throw new Error('Player role not found after removing roles');
255
+ // Update the Player role to have no permissions
256
+ await this.client.role.roleControllerUpdate(playerRole.role.id, {
257
+ name: 'Player',
258
+ permissions: [],
259
+ });
260
+ }
261
+
262
+ // Enable economy for this test
263
+ await this.client.settings.settingsControllerSet('economyEnabled', {
264
+ value: 'true',
265
+ gameServerId: this.setupData.gameserver.id,
266
+ });
267
+ await this.client.settings.settingsControllerSet('currencyName', {
268
+ gameServerId: this.setupData.gameserver.id,
269
+ value: 'test coin',
270
+ });
271
+
272
+ // Set up event listener - just listen for the chat message
273
+ const eventsAwaiter = await new EventsAwaiter().connect(this.client);
274
+ const chatEvents = eventsAwaiter.waitForEvents(IHookEventTypeEnum.ChatMessage, 1);
275
+
276
+ // Try to execute grantCurrency command without permission
277
+ const targetPlayer = this.setupData.players.length > 1 ? this.setupData.players[1] : executingPlayer;
278
+ const commandMsg = `/grantcurrency ${targetPlayer.name} 100`;
279
+
280
+ await this.client.command.commandControllerTrigger(this.setupData.gameserver.id, {
281
+ msg: commandMsg,
282
+ playerId: executingPlayer.id,
283
+ });
284
+
285
+ // Check if we got a permission denied message
286
+ const chatEventResults = await chatEvents;
287
+ expect(chatEventResults).to.have.length(1);
288
+ expect(chatEventResults[0].data.meta.msg).to.match(/do not have permission|need.*permission/i);
289
+ },
290
+ }),
291
+ new IntegrationTest<IModuleTestsSetupData>({
292
+ group,
293
+ snapshot: false,
294
+ setup: modulesTestSetup,
295
+ name: 'Command execution allowed when player has requiredPermissions',
296
+ test: async function () {
297
+ // Install economyUtils module
298
+ await this.client.module.moduleInstallationsControllerInstallModule({
299
+ gameServerId: this.setupData.gameserver.id,
300
+ versionId: this.setupData.economyUtilsModule.latestVersion.id,
301
+ });
302
+
303
+ // Create a role with ECONOMY_UTILS_MANAGE_CURRENCY permission
304
+ const permissions = await this.client.permissionCodesToInputs(['ECONOMY_UTILS_MANAGE_CURRENCY']);
305
+ const roleRes = await this.client.role.roleControllerCreate({
306
+ name: 'Currency Manager',
307
+ permissions,
308
+ });
309
+
310
+ // Assign the role to the player
311
+ await this.client.player.playerControllerAssignRole(this.setupData.players[0].id, roleRes.data.data.id);
312
+
313
+ // Set up event listener for COMMAND_EXECUTED event
314
+ const eventsAwaiter = await new EventsAwaiter().connect(this.client);
315
+ const executedEvents = eventsAwaiter.waitForEvents(IHookEventTypeEnum.CommandExecuted, 1);
316
+
317
+ // Execute grantCurrency command with permission
318
+ const targetPlayer = this.setupData.players.length > 1 ? this.setupData.players[1] : this.setupData.players[0];
319
+
320
+ const commandMsg = `/grantcurrency ${targetPlayer.name} 100`;
321
+
322
+ await this.client.command.commandControllerTrigger(this.setupData.gameserver.id, {
323
+ msg: commandMsg,
324
+ playerId: this.setupData.players[0].id,
325
+ });
326
+
327
+ // Verify COMMAND_EXECUTED event was logged (not COMMAND_EXECUTION_DENIED)
328
+ const executedEventResults = await executedEvents;
329
+ expect(executedEventResults).to.have.length(1);
330
+ expect(executedEventResults[0].data.eventName).to.equal('command-executed');
331
+ // Command might fail during execution, but the important thing is that it was allowed to execute
332
+ // expect(executedEventResults[0].data.command.name).to.equal('grantcurrency');
333
+ // expect(executedEventResults[0].data.player.id).to.equal(this.setupData.players[0].id);
334
+ },
335
+ }),
336
+ new IntegrationTest<IModuleTestsSetupData>({
337
+ group,
338
+ snapshot: false,
339
+ setup: modulesTestSetup,
340
+ name: 'ROOT permission bypasses command permission checks',
341
+ test: async function () {
342
+ // Install economyUtils module
343
+ await this.client.module.moduleInstallationsControllerInstallModule({
344
+ gameServerId: this.setupData.gameserver.id,
345
+ versionId: this.setupData.economyUtilsModule.latestVersion.id,
346
+ });
347
+
348
+ // Create a role with ROOT permission
349
+ const permissions = await this.client.permissionCodesToInputs(['ROOT']);
350
+ const roleRes = await this.client.role.roleControllerCreate({
351
+ name: 'Root Admin Test',
352
+ permissions,
353
+ });
354
+
355
+ // Remove existing roles and assign ROOT role
356
+ await this.client.player.playerControllerRemoveRole(this.setupData.players[0].id, this.setupData.role.id);
357
+ await this.client.player.playerControllerAssignRole(this.setupData.players[0].id, roleRes.data.data.id);
358
+
359
+ // Set up event listener for COMMAND_EXECUTED event
360
+ const eventsAwaiter = await new EventsAwaiter().connect(this.client);
361
+ const executedEvents = eventsAwaiter.waitForEvents(IHookEventTypeEnum.CommandExecuted, 1);
362
+
363
+ // Execute grantCurrency command with ROOT permission (should bypass permission check)
364
+ const targetPlayer = this.setupData.players.length > 1 ? this.setupData.players[1] : this.setupData.players[0];
365
+
366
+ const commandMsg = `/grantcurrency ${targetPlayer.name} 100`;
367
+
368
+ await this.client.command.commandControllerTrigger(this.setupData.gameserver.id, {
369
+ msg: commandMsg,
370
+ playerId: this.setupData.players[0].id,
371
+ });
372
+
373
+ // Verify command was executed successfully
374
+ const executedEventResults = await executedEvents;
375
+ expect(executedEventResults).to.have.length(1);
376
+ expect(executedEventResults[0].data.eventName).to.equal('command-executed');
377
+ // Command might fail during execution, but the important thing is that ROOT bypassed the permission check
378
+ // expect(executedEventResults[0].data.command.name).to.equal('grantcurrency');
193
379
  },
194
380
  }),
195
381
  ];