128 lines
4.1 KiB
JavaScript
128 lines
4.1 KiB
JavaScript
'use strict';
|
||||
|
|
|
|||
|
|
const test = require('node:test');
|
|||
|
|
const assert = require('node:assert/strict');
|
|||
|
|
|
|||
|
|
const {
|
|||
|
|
decideLobbySafety,
|
|||
|
|
findAdminMember,
|
|||
|
|
normalizeIdentity,
|
|||
|
|
toDotaLobbyDetails,
|
|||
|
|
validateLobbySettings,
|
|||
|
|
} = require('../lib/lobby-settings');
|
|||
|
|
const { parseWorkshopPublishData } = require('../lib/workshop-metadata');
|
|||
|
|
|
|||
|
|
function validSettings(overrides = {}) {
|
|||
|
|
return {
|
|||
|
|
lobbyName: 'Long queue lobby',
|
|||
|
|
serverRegion: 3,
|
|||
|
|
customGameId: '123456789012345678',
|
|||
|
|
customGameMode: 'my_addon',
|
|||
|
|
customMapName: 'my_map',
|
|||
|
|
customMinPlayers: 2,
|
|||
|
|
customMaxPlayers: 10,
|
|||
|
|
notifyAtPlayers: 8,
|
|||
|
|
leaveAtPlayers: 9,
|
|||
|
|
adminNames: 'Alice, Bob',
|
|||
|
|
...overrides,
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
test('validates per-lobby thresholds excluding the bot slot', () => {
|
|||
|
|
const settings = validateLobbySettings(validSettings({ leaveAtPlayers: 9 }));
|
|||
|
|
assert.equal(settings.leaveAtPlayers, 9);
|
|||
|
|
assert.equal(settings.customMaxPlayers, 10);
|
|||
|
|
assert.deepEqual(settings.adminNames, ['Alice', 'Bob']);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
test('rejects a leave threshold above the custom-game player maximum', () => {
|
|||
|
|
assert.throws(
|
|||
|
|
() => validateLobbySettings(validSettings({ leaveAtPlayers: 11 })),
|
|||
|
|
/cannot exceed the custom-game maximum/,
|
|||
|
|
);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
test('rejects an unreachable leave threshold while the lobby owner occupies a slot', () => {
|
|||
|
|
assert.throws(
|
|||
|
|
() => validateLobbySettings(validSettings({ leaveAtPlayers: 10 })),
|
|||
|
|
/must be below the custom-game maximum/,
|
|||
|
|
);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
test('rejects notification after auto-leave', () => {
|
|||
|
|
assert.throws(
|
|||
|
|
() => validateLobbySettings(validSettings({ notifyAtPlayers: 9, leaveAtPlayers: 8 })),
|
|||
|
|
/must not exceed/,
|
|||
|
|
);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
test('normalizes unicode persona names and accepts SteamID64 as an admin identity', () => {
|
|||
|
|
const settings = validateLobbySettings(validSettings({ adminNames: ['Alice', '76561198000000000'] }));
|
|||
|
|
const byName = findAdminMember(settings, [{ steamId: '1', name: 'Alice' }]);
|
|||
|
|
const byId = findAdminMember(settings, [{ steamId: '76561198000000000', name: 'Renamed' }]);
|
|||
|
|
assert.equal(byName.name, 'Alice');
|
|||
|
|
assert.equal(byId.name, 'Renamed');
|
|||
|
|
assert.equal(normalizeIdentity(' ALICE '), 'alice');
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
test('maps validated settings to current protobuf field names', () => {
|
|||
|
|
const settings = validateLobbySettings(validSettings({
|
|||
|
|
customGameCrc: '42',
|
|||
|
|
customGameTimestamp: '1234',
|
|||
|
|
}));
|
|||
|
|
assert.deepEqual(toDotaLobbyDetails(settings), {
|
|||
|
|
gameName: 'Long queue lobby',
|
|||
|
|
serverRegion: 3,
|
|||
|
|
gameMode: 15,
|
|||
|
|
allowCheats: false,
|
|||
|
|
fillWithBots: false,
|
|||
|
|
allowSpectating: true,
|
|||
|
|
passKey: '',
|
|||
|
|
lan: false,
|
|||
|
|
customGameMode: 'my_addon',
|
|||
|
|
customMapName: 'my_map',
|
|||
|
|
customDifficulty: 0,
|
|||
|
|
customGameId: '123456789012345678',
|
|||
|
|
customMinPlayers: 2,
|
|||
|
|
customMaxPlayers: 10,
|
|||
|
|
visibility: 0,
|
|||
|
|
customGameCrc: '42',
|
|||
|
|
customGameTimestamp: 1234,
|
|||
|
|
customGamePenalties: true,
|
|||
|
|
});
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
test('rejects a public custom lobby without its internal addon name', () => {
|
|||
|
|
assert.throws(
|
|||
|
|
() => validateLobbySettings(validSettings({ customGameMode: '' })),
|
|||
|
|
/Internal addon name is required/,
|
|||
|
|
);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
test('leaves at the selected real-player count before doing name lookups', () => {
|
|||
|
|
const settings = validateLobbySettings(validSettings({ notifyAtPlayers: 2, leaveAtPlayers: 3 }));
|
|||
|
|
const members = [
|
|||
|
|
{ steamId: '1', name: '' },
|
|||
|
|
{ steamId: '2', name: '' },
|
|||
|
|
{ steamId: '3', name: '' },
|
|||
|
|
];
|
|||
|
|
assert.deepEqual(decideLobbySafety(settings, members, 0), { action: 'player-limit', admin: null });
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
test('never recommends an abandon-style leave after lobby launch starts', () => {
|
|||
|
|
const settings = validateLobbySettings(validSettings({ notifyAtPlayers: 2, leaveAtPlayers: 3 }));
|
|||
|
|
const members = Array.from({ length: 3 }, (_, i) => ({ steamId: String(i + 1), name: '' }));
|
|||
|
|
assert.deepEqual(decideLobbySafety(settings, members, 1), { action: 'unsafe-state', admin: null });
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
test('reads the internal addon name from Steam Workshop publish_data', () => {
|
|||
|
|
assert.deepEqual(parseWorkshopPublishData(`"publish_data"
|
|||
|
|
{
|
|||
|
|
"source_folder" "custom_hero_clash"
|
|||
|
|
"publish_time" "1785566650"
|
|||
|
|
}`), {
|
|||
|
|
sourceFolder: 'custom_hero_clash',
|
|||
|
|
publishTime: '1785566650',
|
|||
|
|
});
|
|||
|
|
});
|