mirror of
https://github.com/bitburner-official/bitburner-src.git
synced 2026-04-17 14:59:16 +02:00
DARKNET: Darkweb Expansion Project & Bitnode (#2139)
This is BN15. It is a really big change; see the PR for all the details.
This commit is contained in:
committed by
GitHub
parent
a674633f6c
commit
6073964768
751
test/jest/Darknet/Darknet.test.ts
Normal file
751
test/jest/Darknet/Darknet.test.ts
Normal file
@@ -0,0 +1,751 @@
|
||||
import {
|
||||
serverFactory,
|
||||
getEchoVulnConfig,
|
||||
getNoPasswordConfig,
|
||||
getDefaultPasswordConfig,
|
||||
getMastermindHintConfig,
|
||||
encodeNumberInBaseN,
|
||||
parseBaseNNumberString,
|
||||
getConvertToBase10Config,
|
||||
parseSimpleArithmeticExpression,
|
||||
generateSimpleArithmeticExpression,
|
||||
getLargestPrimeFactorConfig,
|
||||
largePrimes,
|
||||
getDivisibilityTestConfig,
|
||||
getRomanNumeralConfig,
|
||||
romanNumeralDecoder,
|
||||
getBufferOverflowConfig,
|
||||
getParseArithmeticExpressionConfig,
|
||||
getBinaryEncodedConfig,
|
||||
getTimingAttackConfig,
|
||||
getSpiceLevelConfig,
|
||||
getGuessNumberConfig,
|
||||
getCaptchaConfig,
|
||||
getYesn_tConfig,
|
||||
cleanArithmeticExpression,
|
||||
getXorMaskEncryptedPasswordConfig,
|
||||
getTripleModuloConfig,
|
||||
getKingOfTheHillConfig,
|
||||
} from "../../../src/DarkNet/controllers/ServerGenerator";
|
||||
import {
|
||||
commonPasswordDictionary,
|
||||
connectors,
|
||||
defaultSettingsDictionary,
|
||||
l33t,
|
||||
lettersLowercase,
|
||||
lettersUppercase,
|
||||
loreNames,
|
||||
notebookFileNames,
|
||||
numbers,
|
||||
passwordFileNames,
|
||||
presetNames,
|
||||
ServerNamePrefixes,
|
||||
ServerNameSuffixes,
|
||||
} from "../../../src/DarkNet/models/dictionaryData";
|
||||
import { getAuthResult, isCloseToCorrectPassword } from "../../../src/DarkNet/effects/authentication";
|
||||
import { DarknetState } from "../../../src/DarkNet/models/DarknetState";
|
||||
import { GenericResponseMessage, ResponseCodeEnum } from "../../../src/DarkNet/Enums";
|
||||
import { expectWithMessage, getNS, initGameEnvironment, setupBasicTestingEnvironment } from "../Utilities";
|
||||
import { getClueFileName, getDarkscapeNavigator } from "../../../src/DarkNet/effects/effects";
|
||||
import * as exceptionAlertModule from "../../../src/utils/helpers/exceptionAlert";
|
||||
import * as UtilityModule from "../../../src/utils/Utility";
|
||||
import { mutateDarknet } from "../../../src/DarkNet/controllers/NetworkMovement";
|
||||
import { launchWebstorm } from "../../../src/DarkNet/effects/webstorm";
|
||||
import { isNumber } from "../../../src/types";
|
||||
import { getMostRecentAuthLog, getServerLogs } from "../../../src/DarkNet/models/packetSniffing";
|
||||
import { Player } from "@player";
|
||||
import { assertString } from "../../../src/utils/TypeAssertion";
|
||||
import { assertPasswordResponse, generateDarknetServerName } from "../../../src/DarkNet/models/DarknetServerOptions";
|
||||
import { DarknetServer } from "../../../src/Server/DarknetServer";
|
||||
import { isDirectoryPath } from "../../../src/Paths/Directory";
|
||||
import { isFilePath } from "../../../src/Paths/FilePath";
|
||||
import { LAB_CACHE_NAME } from "../../../src/DarkNet/effects/labyrinth";
|
||||
import { generateCacheFilename } from "../../../src/DarkNet/effects/cacheFiles";
|
||||
|
||||
beforeAll(() => {
|
||||
initGameEnvironment();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
setupBasicTestingEnvironment();
|
||||
getDarkscapeNavigator();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
const getLatestResponseTime = (server: DarknetServer) => {
|
||||
const lastPasswordResponse = DarknetState.serverState[server.hostname].serverLogs[0].message;
|
||||
assertPasswordResponse(lastPasswordResponse);
|
||||
assertString(lastPasswordResponse.data);
|
||||
const timeMatch = lastPasswordResponse.data.match(/Response time: (\d+\.?\d*)ms/);
|
||||
if (!timeMatch) {
|
||||
throw new Error(`No response time found in log: ${JSON.stringify(lastPasswordResponse)}`);
|
||||
}
|
||||
const responseTime = Number(timeMatch[1]);
|
||||
if (!Number.isFinite(responseTime)) {
|
||||
throw new Error(`No response time found in log: ${JSON.stringify(lastPasswordResponse)}`);
|
||||
}
|
||||
return responseTime;
|
||||
};
|
||||
|
||||
describe("Password Tests", () => {
|
||||
const difficulty = 1;
|
||||
|
||||
test("getEchoVulnServer creates a server and checks password correctly", () => {
|
||||
const server = serverFactory(getEchoVulnConfig, difficulty, 0, 0);
|
||||
expect(server).toBeDefined();
|
||||
const failedAttemptResponse = getAuthResult(server, "wrongPassword", 1);
|
||||
expect(failedAttemptResponse.result.code).toBe(ResponseCodeEnum.AuthFailure);
|
||||
expect(failedAttemptResponse.response.message.includes(server.password)).toBe(true);
|
||||
expect(server.hasAdminRights).toBe(false);
|
||||
|
||||
expect(getAuthResult(server, server.password, 1).result.code).toBe(ResponseCodeEnum.Success);
|
||||
expect(server.hasAdminRights).toBe(true);
|
||||
});
|
||||
|
||||
test("getNoPasswordServer creates a server with no password", () => {
|
||||
const server = serverFactory(getNoPasswordConfig, difficulty, 0, 0);
|
||||
expect(server).toBeDefined();
|
||||
const failedAttemptResponse = getAuthResult(server, "wrongPassword", 1);
|
||||
expect(failedAttemptResponse.result.code).toBe(ResponseCodeEnum.AuthFailure);
|
||||
expect(server.hasAdminRights).toBe(false);
|
||||
|
||||
expect(getAuthResult(server, server.password, 1).result.code).toBe(ResponseCodeEnum.Success);
|
||||
expect(server.hasAdminRights).toBe(true);
|
||||
});
|
||||
|
||||
test("getDefaultPasswordServer creates a server with default password", () => {
|
||||
const server = serverFactory(getDefaultPasswordConfig, difficulty, 0, 0);
|
||||
expect(server).toBeDefined();
|
||||
const failedAttemptResponse = getAuthResult(server, "wrongPassword", 1);
|
||||
|
||||
expect(failedAttemptResponse.result.code).toBe(ResponseCodeEnum.AuthFailure);
|
||||
expect(server.hasAdminRights).toBe(false);
|
||||
|
||||
expect((defaultSettingsDictionary as unknown as string[]).includes(server.password)).toBe(true);
|
||||
|
||||
expect(getAuthResult(server, server.password, 1).result.code).toBe(ResponseCodeEnum.Success);
|
||||
expect(server.hasAdminRights).toBe(true);
|
||||
});
|
||||
|
||||
test("getMastermindHintServer creates a server with mastermind hint", () => {
|
||||
const password = "11223334";
|
||||
const server = serverFactory(getMastermindHintConfig, difficulty, 0, 0);
|
||||
server.password = password;
|
||||
expect(server).toBeDefined();
|
||||
|
||||
const getData = () => {
|
||||
const authLog = getMostRecentAuthLog(server.hostname);
|
||||
if (!authLog) {
|
||||
throw new Error(`Cannot find the most recent auth log in ${server.hostname}`);
|
||||
}
|
||||
if (!authLog.data) {
|
||||
throw new Error(`Auth log does not contain data: ${JSON.stringify(authLog)}`);
|
||||
}
|
||||
assertString(authLog.data);
|
||||
return authLog.data.split(",").map((item) => item.trim());
|
||||
};
|
||||
|
||||
const failedAttemptResponse1 = getAuthResult(server, "");
|
||||
expect(failedAttemptResponse1.response.code).toBe(ResponseCodeEnum.AuthFailure);
|
||||
expect(server.hasAdminRights).toBe(false);
|
||||
|
||||
const [correctCount1, closeCount1] = getData();
|
||||
expect(correctCount1).toBe("0");
|
||||
expect(closeCount1).toBe("0");
|
||||
|
||||
const failedAttemptResponse2 = getAuthResult(server, "123");
|
||||
expect(failedAttemptResponse2.response.code).toBe(ResponseCodeEnum.AuthFailure);
|
||||
const [correctCount2, closeCount2] = getData();
|
||||
expect(correctCount2).toBe("1");
|
||||
expect(closeCount2).toBe("2");
|
||||
|
||||
const failedAttemptResponse3 = getAuthResult(server, "11111111");
|
||||
expect(failedAttemptResponse3.response.code).toBe(ResponseCodeEnum.AuthFailure);
|
||||
const [correctCount3, closeCount3] = getData();
|
||||
expect(correctCount3).toBe("2");
|
||||
expect(closeCount3).toBe("0");
|
||||
|
||||
const failedAttemptResponse4 = getAuthResult(server, "1122334");
|
||||
expect(failedAttemptResponse4.response.code).toBe(ResponseCodeEnum.AuthFailure);
|
||||
const [correctCount4, closeCount4] = getData();
|
||||
expect(correctCount4).toBe("6");
|
||||
expect(closeCount4).toBe("1");
|
||||
|
||||
const failedAttemptResponse5 = getAuthResult(server, "22114333");
|
||||
expect(failedAttemptResponse5.response.code).toBe(ResponseCodeEnum.AuthFailure);
|
||||
expect(server.hasAdminRights).toBe(false);
|
||||
const [correctCount5, closeCount5] = getData();
|
||||
expect(correctCount5).toBe("2");
|
||||
expect(closeCount5).toBe("6");
|
||||
|
||||
server.password = "2435";
|
||||
const failedAttemptResponse6 = getAuthResult(server, "3423");
|
||||
expect(failedAttemptResponse6.response.code).toBe(ResponseCodeEnum.AuthFailure);
|
||||
expect(server.hasAdminRights).toBe(false);
|
||||
const [correctCount6, closeCount6] = getData();
|
||||
expect(correctCount6).toBe("1");
|
||||
expect(closeCount6).toBe("2");
|
||||
|
||||
expect(getAuthResult(server, server.password, 1).result.code).toBe(ResponseCodeEnum.Success);
|
||||
expect(server.hasAdminRights).toBe(true);
|
||||
});
|
||||
|
||||
test("getConvertToBase10Server creates a server with a correct password hint", () => {
|
||||
const server = serverFactory(getConvertToBase10Config, 5, 0, 0);
|
||||
expect(server).toBeDefined();
|
||||
const failedAttemptResponse = getAuthResult(server, "wrongPassword", 1);
|
||||
expect(failedAttemptResponse.result.code).toBe(ResponseCodeEnum.AuthFailure);
|
||||
|
||||
if (!failedAttemptResponse.response.data) {
|
||||
throw new Error(`Password response does not contain data: ${JSON.stringify(failedAttemptResponse.response)}`);
|
||||
}
|
||||
assertString(failedAttemptResponse.response.data);
|
||||
const [base, numberString] = failedAttemptResponse.response.data.split(",");
|
||||
expect(numberString).toBe(encodeNumberInBaseN(+server.password, Number(base)));
|
||||
|
||||
const attemptedPassword = Number.parseInt(numberString, Number(base));
|
||||
|
||||
const result = getAuthResult(server, `${attemptedPassword}`, 1);
|
||||
|
||||
expect(isCloseToCorrectPassword(server.password, attemptedPassword, true)).toBe(true);
|
||||
expect(result.response.code).toBe(ResponseCodeEnum.Success);
|
||||
});
|
||||
|
||||
test("getConvertToBase10Server creates a server with a correct password hint, and has a non-integer solution at higher difficulties ", () => {
|
||||
const server = serverFactory(getConvertToBase10Config, 15, 0, 0);
|
||||
expect(server).toBeDefined();
|
||||
const failedAttemptResponse = getAuthResult(server, "wrongPassword", 1);
|
||||
expect(failedAttemptResponse.result.code).toBe(ResponseCodeEnum.AuthFailure);
|
||||
|
||||
if (!failedAttemptResponse.response.data) {
|
||||
throw new Error(`Password response does not contain data: ${JSON.stringify(failedAttemptResponse.response)}`);
|
||||
}
|
||||
assertString(failedAttemptResponse.response.data);
|
||||
const [base, numberString] = failedAttemptResponse.response.data.split(",");
|
||||
expect(numberString).toBe(encodeNumberInBaseN(+server.password, Number(base)));
|
||||
|
||||
// custom parser is used to handle non-integer answers
|
||||
const attemptedPassword = parseBaseNNumberString(numberString, Number(base));
|
||||
|
||||
const result = getAuthResult(server, `${attemptedPassword}`, 1);
|
||||
|
||||
expect(isCloseToCorrectPassword(server.password, attemptedPassword)).toBe(true);
|
||||
expect(result.response.code).toBe(ResponseCodeEnum.Success);
|
||||
});
|
||||
|
||||
test("encodeNumberInBaseN and parseBaseNNumberString encode/decode numbers correctly", () => {
|
||||
expect(encodeNumberInBaseN(15, 5.5)).toBe("24");
|
||||
expect(encodeNumberInBaseN(16, 5.5)).toBe("25");
|
||||
expect(encodeNumberInBaseN(17, 5.5)).toBe("30.24034");
|
||||
expect(encodeNumberInBaseN(264, 17.6)).toBe("EH.A9F");
|
||||
|
||||
expect(parseBaseNNumberString("24", 5.5)).toBe(15);
|
||||
expect(parseBaseNNumberString("25", 5.5)).toBe(16);
|
||||
|
||||
const aprox = parseBaseNNumberString("30.24", 5.5);
|
||||
expect(Math.abs(aprox - 17) < 0.1).toBe(true);
|
||||
|
||||
expect(encodeNumberInBaseN(7, 2)).toBe("111");
|
||||
expect(encodeNumberInBaseN(112, 2)).toBe("1110000");
|
||||
});
|
||||
|
||||
test("parseSimpleArithmeticExpression parses expressions correctly", () => {
|
||||
expect(parseSimpleArithmeticExpression("1 + 2")).toBe(3);
|
||||
expect(parseSimpleArithmeticExpression("1 - 2")).toBe(-1);
|
||||
expect(parseSimpleArithmeticExpression("5 + 1 * 3")).toBe(8);
|
||||
expect(parseSimpleArithmeticExpression("5 * ( 6 + 7 )")).toBe(65);
|
||||
expect(parseSimpleArithmeticExpression("4 + 5 * ( 6 + 7 ) / 2")).toBe(36.5);
|
||||
expect(parseSimpleArithmeticExpression("1 + 3 * ( 4 / 5 ) / 2 + 4 ")).toBe(6.2);
|
||||
expect(Math.abs(parseSimpleArithmeticExpression("1 + 3 * ((4 / 5) / 2 ) * 3 + 4 ")) - 8.6 < 0.01).toBe(true);
|
||||
expect(
|
||||
Math.abs(
|
||||
parseSimpleArithmeticExpression(
|
||||
"23 * ( 41 + 76 + 32 * 27 * 6 ) - 34 - 49 + 93 - ( 11 / 41 - 62 / 6 + 5 ) * 19 - 0",
|
||||
),
|
||||
) -
|
||||
122029.235 <
|
||||
0.9,
|
||||
).toBe(true);
|
||||
expect(parseSimpleArithmeticExpression("48 - 38 * 24 + ( 72 / 8 * 4 ) - 76 * 61 * 16")).toBe(-75004);
|
||||
expect(
|
||||
Math.ceil(parseSimpleArithmeticExpression("8 / 15 / 91 / ( 54 * 10 * 84 ) - 77 * 83 + ( 83 * 75 / 8 ) + 54")),
|
||||
).toBe(-5558);
|
||||
expect(
|
||||
parseSimpleArithmeticExpression(
|
||||
"37 / 8 / 81 / ( 1 + ( 80 * 31 ) - 26 - 53 ) / 52 / ( 18 * 72 / 78 ) * 83 * ( 21 * 88 + 96 ) + 23",
|
||||
),
|
||||
).toBeCloseTo(24.61, -1);
|
||||
expect(parseSimpleArithmeticExpression("94 / ( 76 * 63 * ( 89 * 33 ) + 70 ) * 13 * 73 * 61 * 81 * 74")).toBeCloseTo(
|
||||
2319.425,
|
||||
);
|
||||
expect(
|
||||
parseSimpleArithmeticExpression(
|
||||
"94 / ( 76 * 63 * ( 89 * 33 ) + 70 ) * 13 * 73 * 61 * 81 * 74;alert('injection!')",
|
||||
),
|
||||
).toBeCloseTo(2319.425);
|
||||
|
||||
const value = parseSimpleArithmeticExpression("76 + 30 * ( 3 * 10 + 14 ) - 83 / 47 + 16");
|
||||
expect(isCloseToCorrectPassword(value.toString(), 1410.2340425531916)).toBe(true);
|
||||
|
||||
const value2 = parseSimpleArithmeticExpression(
|
||||
" 5 ÷ ( 30 ➕ 64 ➕ 11 ) ҳ 98 ÷ 17 ➕ 20 ➕ 58 ➖ ( 64 ➖ 4 ҳ 80 ) ҳ 90",
|
||||
);
|
||||
expect(isCloseToCorrectPassword(value2.toString(), 23118.274509803923)).toBe(true);
|
||||
|
||||
const expression = generateSimpleArithmeticExpression(20);
|
||||
expect(eval(cleanArithmeticExpression(expression))).toBeCloseTo(parseSimpleArithmeticExpression(expression));
|
||||
});
|
||||
|
||||
test("getParseArithmeticExpressionConfig server creates a server with a correct password hint", () => {
|
||||
const server = serverFactory(() => getParseArithmeticExpressionConfig(25), 25, 0, 0);
|
||||
expect(server).toBeDefined();
|
||||
const failedAttemptResponse = getAuthResult(server, "wrongPassword", 1);
|
||||
expect(failedAttemptResponse.result.code).toBe(ResponseCodeEnum.AuthFailure);
|
||||
|
||||
const expression = failedAttemptResponse.response.data;
|
||||
const attemptedPassword = parseSimpleArithmeticExpression(`${expression}`);
|
||||
|
||||
const result = getAuthResult(server, `${attemptedPassword}`, 1);
|
||||
|
||||
expect(isCloseToCorrectPassword(server.password, attemptedPassword, true)).toBe(true);
|
||||
expect(result.response.code).toBe(ResponseCodeEnum.Success);
|
||||
});
|
||||
|
||||
test("getBinaryEncodedConfig server creates a server with a correct password hint", () => {
|
||||
const server = serverFactory(() => getBinaryEncodedConfig(20), 20, 0, 0);
|
||||
expect(server).toBeDefined();
|
||||
const failedAttemptResponse = getAuthResult(server, "wrongPassword", 1);
|
||||
expect(failedAttemptResponse.result.code).toBe(ResponseCodeEnum.AuthFailure);
|
||||
|
||||
const binaryString = failedAttemptResponse.response.data;
|
||||
|
||||
// convert each 8 bits to a character, then join
|
||||
const attemptedPassword = String.fromCharCode(...`${binaryString}`.split(" ").map((byte) => parseInt(byte, 2)));
|
||||
const result = getAuthResult(server, `${attemptedPassword}`, 1);
|
||||
expect(attemptedPassword).toBe(server.password);
|
||||
expect(result.response.code).toBe(ResponseCodeEnum.Success);
|
||||
});
|
||||
|
||||
test("getXorMaskEncryptedPasswordConfig server creates a server with a correct password hint", () => {
|
||||
const server = serverFactory(() => getXorMaskEncryptedPasswordConfig(), 20, 0, 0);
|
||||
expect(server).toBeDefined();
|
||||
const failedAttemptResponse = getAuthResult(server, "wrongPassword", 1);
|
||||
expect(failedAttemptResponse.result.code).toBe(ResponseCodeEnum.AuthFailure);
|
||||
|
||||
const xorData = failedAttemptResponse.response.data;
|
||||
if (!xorData) {
|
||||
throw new Error(`Password response does not contain data: ${JSON.stringify(failedAttemptResponse.response)}`);
|
||||
}
|
||||
assertString(xorData);
|
||||
const [encryptedPasswordString, maskString] = xorData.split(";");
|
||||
const mask = maskString.split(" ").map((byte) => parseInt(byte, 2));
|
||||
const encryptedPasswordChars = encryptedPasswordString.split("");
|
||||
|
||||
const attemptedPassword = encryptedPasswordChars
|
||||
.map((char, i) => String.fromCharCode(char.charCodeAt(0) ^ mask[i]))
|
||||
.join("");
|
||||
|
||||
const result = getAuthResult(server, `${attemptedPassword}`, 1);
|
||||
expect(attemptedPassword).toBe(server.password);
|
||||
expect(result.response.code).toBe(ResponseCodeEnum.Success);
|
||||
});
|
||||
|
||||
test("getTimingAttackConfig server creates a server that takes longer the more correct characters are submitted, starting from the left", async () => {
|
||||
Player.gainCharismaExp(1e200);
|
||||
const server = serverFactory(getTimingAttackConfig, 20, 0, 0);
|
||||
server.logTrafficInterval = -1;
|
||||
const ns = getNS(server.hostname);
|
||||
expect(server).toBeDefined();
|
||||
const failedAttemptResponse = getAuthResult(server, "$$$$$$$", 1);
|
||||
expect(failedAttemptResponse.result.code).toBe(ResponseCodeEnum.AuthFailure);
|
||||
|
||||
const response1 = await ns.dnet.authenticate(server.hostname, server.password.slice(0, 1) + "%%%%", 0);
|
||||
const firstAuthTime = getLatestResponseTime(server);
|
||||
const response2 = await ns.dnet.authenticate(server.hostname, server.password.slice(0, 2) + "%%%", 0);
|
||||
const secondAuthTime = getLatestResponseTime(server);
|
||||
const response3 = await ns.dnet.authenticate(server.hostname, server.password.slice(0, 3) + "%%", 0);
|
||||
const thirdAuthTime = getLatestResponseTime(server);
|
||||
const response4WithWorseGuess = await ns.dnet.authenticate(server.hostname, "%%%%%%", 0);
|
||||
const badAuthTime = getLatestResponseTime(server);
|
||||
|
||||
expect(badAuthTime).toBeLessThan(firstAuthTime);
|
||||
expect(secondAuthTime).toBeGreaterThan(firstAuthTime);
|
||||
expect(thirdAuthTime).toBeGreaterThan(secondAuthTime);
|
||||
|
||||
expect(response1.code).toBe(ResponseCodeEnum.AuthFailure);
|
||||
expect(response2.code).toBe(ResponseCodeEnum.AuthFailure);
|
||||
expect(response3.code).toBe(ResponseCodeEnum.AuthFailure);
|
||||
expect(response4WithWorseGuess.code).toBe(ResponseCodeEnum.AuthFailure);
|
||||
|
||||
expect(response1.message).toBe(GenericResponseMessage.AuthFailure);
|
||||
|
||||
const serverLogs = DarknetState.serverState[server.hostname].serverLogs;
|
||||
expect(serverLogs.length).toBe(5);
|
||||
assertPasswordResponse(serverLogs[4].message);
|
||||
assertPasswordResponse(serverLogs[3].message);
|
||||
assertPasswordResponse(serverLogs[2].message);
|
||||
assertPasswordResponse(serverLogs[1].message);
|
||||
assertPasswordResponse(serverLogs[0].message);
|
||||
expect(serverLogs[4].message.message).toBe("Found a mismatch while checking each character (0)");
|
||||
expect(serverLogs[3].message.message).toBe("Found a mismatch while checking each character (1)");
|
||||
expect(serverLogs[2].message.message).toBe("Found a mismatch while checking each character (2)");
|
||||
expect(serverLogs[1].message.message).toBe("Found a mismatch while checking each character (3)");
|
||||
expect(serverLogs[0].message.message).toBe("Found a mismatch while checking each character (0)");
|
||||
|
||||
const successfulResponse = await ns.dnet.authenticate(server.hostname, server.password, 0);
|
||||
expect(successfulResponse.code).toBe(ResponseCodeEnum.Success);
|
||||
});
|
||||
|
||||
test("getTimingAttackConfig server only gives response time improvements on the exact correct character", () => {
|
||||
const server = serverFactory(getTimingAttackConfig, 20, 0, 0);
|
||||
server.logTrafficInterval = -1;
|
||||
expect(server).toBeDefined();
|
||||
|
||||
getAuthResult(server, server.password.slice(0, 3), 0);
|
||||
const baseAuthTime = getLatestResponseTime(server);
|
||||
expect(getServerLogs(server, 10).length).toBe(1);
|
||||
|
||||
for (const char of [...lettersUppercase, ...lettersLowercase, ...numbers]) {
|
||||
if (char === server.password[1]) continue;
|
||||
getAuthResult(server, server.password.slice(0, 3) + char, 0);
|
||||
const testAuthTime = getLatestResponseTime(server);
|
||||
expect(testAuthTime).toBe(baseAuthTime);
|
||||
expect(getServerLogs(server, 10).length).toBe(1);
|
||||
}
|
||||
});
|
||||
|
||||
test("getSpiceLevelConfig server creates a server with a correct password hint", () => {
|
||||
const server = serverFactory(getSpiceLevelConfig, 20, 0, 0);
|
||||
expect(server).toBeDefined();
|
||||
const failedAttemptResponse = getAuthResult(server, "wrongPassword", 1);
|
||||
expect(failedAttemptResponse.result.code).toBe(ResponseCodeEnum.AuthFailure);
|
||||
|
||||
const correctChars = server.password.slice(0, 3);
|
||||
|
||||
const result1 = getAuthResult(server, `${correctChars}%%%%%%`, 1);
|
||||
expect(result1.response.code).toBe(ResponseCodeEnum.AuthFailure);
|
||||
expect(result1.response.message).toBe("Not spicy enough");
|
||||
expect(result1.response.data).toBe("🌶️🌶️🌶️/10");
|
||||
|
||||
const result2 = getAuthResult(server, `%%%%%%%%`, 1);
|
||||
expect(result2.response.code).toBe(ResponseCodeEnum.AuthFailure);
|
||||
expect(result2.response.message).toBe("Not spicy enough");
|
||||
expect(result2.response.data).toBe("0/10");
|
||||
|
||||
const result3 = getAuthResult(server, `${correctChars.slice(0, 2)}%%%%`, 1);
|
||||
expect(result3.response.code).toBe(ResponseCodeEnum.AuthFailure);
|
||||
expect(result3.response.message).toBe("Not spicy enough");
|
||||
expect(result3.response.data).toBe("🌶️🌶️/10");
|
||||
|
||||
const result4 = getAuthResult(server, server.password, 1);
|
||||
expect(result4.response.code).toBe(ResponseCodeEnum.Success);
|
||||
});
|
||||
|
||||
test("getGuessNumberConfig server creates a server with a correct password hint", () => {
|
||||
const server = serverFactory(getGuessNumberConfig, 20, 0, 0);
|
||||
expect(server).toBeDefined();
|
||||
const failedAttemptResponse = getAuthResult(server, "wrongPassword", 1);
|
||||
expect(failedAttemptResponse.result.code).toBe(ResponseCodeEnum.AuthFailure);
|
||||
|
||||
const response1 = getAuthResult(server, `${+server.password - 10}`, 1);
|
||||
expect(response1.response.code).toBe(ResponseCodeEnum.AuthFailure);
|
||||
expect(response1.response.data).toBe("Higher");
|
||||
|
||||
const response2 = getAuthResult(server, `${+server.password + 10}`, 1);
|
||||
expect(response2.response.code).toBe(ResponseCodeEnum.AuthFailure);
|
||||
expect(response2.response.data).toBe("Lower");
|
||||
|
||||
const response3 = getAuthResult(server, server.password, 1);
|
||||
expect(response3.response.code).toBe(ResponseCodeEnum.Success);
|
||||
});
|
||||
|
||||
test("getCaptchaConfig server creates a server with a correct password hint", () => {
|
||||
const server = serverFactory(getCaptchaConfig, 20, 0, 0);
|
||||
expect(server).toBeDefined();
|
||||
const failedAttemptResponse = getAuthResult(server, "wrongPassword", 1);
|
||||
expect(failedAttemptResponse.result.code).toBe(ResponseCodeEnum.AuthFailure);
|
||||
|
||||
const password = server.passwordHintData
|
||||
.split("")
|
||||
.filter((c) => !isNaN(+c))
|
||||
.join("");
|
||||
const result = getAuthResult(server, `${password}`, 1);
|
||||
expect(result.result.success).toBe(true);
|
||||
});
|
||||
|
||||
test("getYesn_tConfig server creates a server with a correct password hint", () => {
|
||||
const server = serverFactory(getYesn_tConfig, 20, 0, 0);
|
||||
expect(server).toBeDefined();
|
||||
const failedAttemptResponse = getAuthResult(server, "wrongPassword", 1);
|
||||
expect(failedAttemptResponse.result.code).toBe(ResponseCodeEnum.AuthFailure);
|
||||
|
||||
const result1 = getAuthResult(server, server.password.slice(0, 2) + "%%%%%", 1);
|
||||
expect(result1.response.code).toBe(ResponseCodeEnum.AuthFailure);
|
||||
expect(result1.response.data).toBe("yes,yes,yesn't,yesn't,yesn't,yesn't,yesn't");
|
||||
|
||||
const result2 = getAuthResult(server, `%%%${server.password.slice(3, 5)}%%%%%`, 1);
|
||||
expect(result2.response.code).toBe(ResponseCodeEnum.AuthFailure);
|
||||
expect(result2.response.data).toBe("yesn't,yesn't,yesn't,yes,yes,yesn't,yesn't,yesn't,yesn't,yesn't");
|
||||
|
||||
const result3 = getAuthResult(server, server.password, 1);
|
||||
expect(result3.response.code).toBe(ResponseCodeEnum.Success);
|
||||
});
|
||||
|
||||
test("getRomanNumeralsServer creates a server with a correct password hint", () => {
|
||||
const server = serverFactory(getRomanNumeralConfig, 5, 0, 0);
|
||||
expect(server).toBeDefined();
|
||||
const failedAttemptResponse = getAuthResult(server, "wrongPassword", 1);
|
||||
expect(failedAttemptResponse.result.code).toBe(ResponseCodeEnum.AuthFailure);
|
||||
|
||||
const attemptedPassword = romanNumeralDecoder(server.passwordHintData);
|
||||
|
||||
const result = getAuthResult(server, `${attemptedPassword}`, 1);
|
||||
|
||||
expect(result.result.success).toBe(true);
|
||||
expect(result.response.code).toBe(ResponseCodeEnum.Success);
|
||||
});
|
||||
|
||||
test("getLargestPrimeFactor server creates valid password and hint", () => {
|
||||
const server = serverFactory(getLargestPrimeFactorConfig, 20, 0, 0);
|
||||
const password = +server.password;
|
||||
const hint = +server.passwordHintData;
|
||||
|
||||
expect(isNumber(password)).toBe(true);
|
||||
expect(isNumber(hint)).toBe(true);
|
||||
|
||||
const factor = hint / password;
|
||||
expect(factor).toEqual(Math.floor(factor));
|
||||
|
||||
const factors = largePrimes
|
||||
.filter((n) => hint / n === Math.floor(hint / n))
|
||||
.sort()
|
||||
.toReversed();
|
||||
expect(factors[0]).toEqual(password);
|
||||
});
|
||||
|
||||
test("getDivisibilityTestConfig server creates valid password and hint", () => {
|
||||
const server = serverFactory(getDivisibilityTestConfig, 100, 0, 0);
|
||||
|
||||
expect(server.password.includes("+")).toBe(false);
|
||||
expect(isNumber(+server.password)).toBe(true);
|
||||
expect(isNumber(+server.passwordHintData)).toBe(true);
|
||||
|
||||
DarknetState.serverState[server.hostname] = {
|
||||
serverLogs: [],
|
||||
authenticatedPIDs: [],
|
||||
};
|
||||
const nonDivisibleResult = getAuthResult(server, `${server.password + 1}`, 1);
|
||||
expect(nonDivisibleResult.response.code).toBe(ResponseCodeEnum.AuthFailure);
|
||||
expect(nonDivisibleResult.response.message).toContain("not divisible");
|
||||
const authLog = getMostRecentAuthLog(server.hostname);
|
||||
if (!authLog) {
|
||||
throw new Error(`Cannot find the most recent auth log in ${server.hostname}`);
|
||||
}
|
||||
expect(authLog.message).toContain("not divisible");
|
||||
|
||||
let factor = 2;
|
||||
while (+server.password % factor !== 0) {
|
||||
++factor;
|
||||
}
|
||||
|
||||
const divisibleResult = getAuthResult(server, `${factor}`, 1);
|
||||
expect(divisibleResult.response.code).toBe(ResponseCodeEnum.AuthFailure);
|
||||
expect(divisibleResult.response.message).toContain("IS divisible");
|
||||
|
||||
const correctResult = getAuthResult(server, `${server.password}`, 1);
|
||||
expect(correctResult.response.code).toBe(ResponseCodeEnum.Success);
|
||||
});
|
||||
|
||||
test("getTripleModuloConfig server creates valid password and hint", () => {
|
||||
const server = serverFactory(getTripleModuloConfig, 60, 0, 0);
|
||||
|
||||
const guess1 = +server.password + 1;
|
||||
const expectedResult1 = (+server.password % guess1) % (((guess1 - 1) % 32) + 1);
|
||||
const result1 = getAuthResult(server, `${guess1}`, 1);
|
||||
expect(result1.response.code).toBe(ResponseCodeEnum.AuthFailure);
|
||||
expect(getMostRecentAuthLog(server.hostname)?.message).toContain(`= ${expectedResult1}`);
|
||||
expect(getMostRecentAuthLog(server.hostname)?.data).toBe(`${expectedResult1}`);
|
||||
|
||||
const guess2 = +server.password - 1;
|
||||
const expectedResult2 = (+server.password % guess2) % (((guess2 - 1) % 32) + 1);
|
||||
const result2 = getAuthResult(server, `${guess2}`, 1);
|
||||
expect(result2.response.code).toBe(ResponseCodeEnum.AuthFailure);
|
||||
expect(getMostRecentAuthLog(server.hostname)?.message).toContain(`= ${expectedResult2}`);
|
||||
expect(getMostRecentAuthLog(server.hostname)?.data).toBe(`${expectedResult2}`);
|
||||
|
||||
const guess3 = Math.floor(+server.password / 2);
|
||||
const expectedResult3 = (+server.password % guess3) % (((guess3 - 1) % 32) + 1);
|
||||
const result3 = getAuthResult(server, `${guess3}`, 1);
|
||||
expect(result3.response.code).toBe(ResponseCodeEnum.AuthFailure);
|
||||
expect(getMostRecentAuthLog(server.hostname)?.message).toContain(`= ${expectedResult3}`);
|
||||
expect(getMostRecentAuthLog(server.hostname)?.data).toBe(`${expectedResult3}`);
|
||||
|
||||
// Check a known factor
|
||||
server.password = (BigInt(server.password) * BigInt(2)).toString();
|
||||
const guessOfFactor = 2;
|
||||
const expectedResultOfFactor = 0;
|
||||
const resultOfFactor = getAuthResult(server, `${guessOfFactor}`, 1);
|
||||
expect(resultOfFactor.response.code).toBe(ResponseCodeEnum.AuthFailure);
|
||||
expect(getMostRecentAuthLog(server.hostname)?.message).toContain(`= ${expectedResultOfFactor}`);
|
||||
expect(getMostRecentAuthLog(server.hostname)?.data).toBe(`${expectedResultOfFactor}`);
|
||||
|
||||
const correctResult = getAuthResult(server, `${server.password}`, 1);
|
||||
expect(correctResult.response.code).toBe(ResponseCodeEnum.Success);
|
||||
});
|
||||
|
||||
test("bufferOverflow server creates valid password and hint", () => {
|
||||
const bufferOverflowServer = serverFactory(getBufferOverflowConfig, 5, 0, 0);
|
||||
bufferOverflowServer.logTrafficInterval = -1;
|
||||
const passwordLength = bufferOverflowServer.password.length;
|
||||
const failedResult = getAuthResult(bufferOverflowServer, "1", 1);
|
||||
expect(failedResult.response.code).toBe(ResponseCodeEnum.AuthFailure);
|
||||
|
||||
const authLog = getMostRecentAuthLog(bufferOverflowServer.hostname);
|
||||
if (!authLog) {
|
||||
throw new Error(`Cannot find the most recent auth log in ${bufferOverflowServer.hostname}`);
|
||||
}
|
||||
const received = "1".padEnd(passwordLength, "ˍ").slice(0, passwordLength);
|
||||
const expected = "■".repeat(passwordLength);
|
||||
expect(authLog.message).toBe(`auth failed: received '${received}', expected '${expected}'`);
|
||||
expect(authLog.passwordAttempted).toBe("1".padEnd(passwordLength, "ˍ").slice(0, passwordLength));
|
||||
|
||||
const successResult = getAuthResult(bufferOverflowServer, "A".repeat(passwordLength * 2), 1);
|
||||
expect(successResult.response.code).toBe(ResponseCodeEnum.Success);
|
||||
});
|
||||
|
||||
test("kingOfTheHill server creates a valid password and hint", () => {
|
||||
const kingOfTheHillServer = serverFactory(getKingOfTheHillConfig, 60, 0, 0);
|
||||
const password = Number(kingOfTheHillServer.password);
|
||||
|
||||
const result1 = getAuthResult(kingOfTheHillServer, `${password - 50}`, 1);
|
||||
expect(result1.response.code).toBe(ResponseCodeEnum.AuthFailure);
|
||||
const logs1 = getMostRecentAuthLog(kingOfTheHillServer.hostname);
|
||||
const result2 = getAuthResult(kingOfTheHillServer, `${password + 50}`, 1);
|
||||
expect(result2.response.code).toBe(ResponseCodeEnum.AuthFailure);
|
||||
const logs2 = getMostRecentAuthLog(kingOfTheHillServer.hostname);
|
||||
const resultNearPassword = getAuthResult(kingOfTheHillServer, `${password + 1}`, 1);
|
||||
expect(resultNearPassword.response.code).toBe(ResponseCodeEnum.AuthFailure);
|
||||
const logsNearPassword = getMostRecentAuthLog(kingOfTheHillServer.hostname);
|
||||
getAuthResult(kingOfTheHillServer, `${password - 1}`, 1);
|
||||
const logsNearPassword2 = getMostRecentAuthLog(kingOfTheHillServer.hostname);
|
||||
expect(Number(logs1?.data)).toBeLessThan(Number(logsNearPassword?.data));
|
||||
expect(Number(logs2?.data)).toBeLessThan(Number(logsNearPassword?.data));
|
||||
expect(Number(logsNearPassword?.data)).toBeLessThan(10000);
|
||||
expect(Number(logsNearPassword2?.data)).toBeLessThan(10000);
|
||||
|
||||
getAuthResult(kingOfTheHillServer, `12345`, 1);
|
||||
const logs3 = getMostRecentAuthLog(kingOfTheHillServer.hostname);
|
||||
getAuthResult(kingOfTheHillServer, `12345`, 1);
|
||||
const logs4 = getMostRecentAuthLog(kingOfTheHillServer.hostname);
|
||||
expect(Number(logs3?.data)).toBe(Number(logs4?.data));
|
||||
|
||||
// For testing password spreads: poll the altitude and log the resulting graph
|
||||
// const results = [];
|
||||
// for (let attempt = password * 0.6; attempt <= password * 1.4; attempt += password/200) {
|
||||
// getAuthResult(kingOfTheHillServer, `${attempt}`, 1);
|
||||
// const data = getMostRecentAuthLog(kingOfTheHillServer.hostname)?.data;
|
||||
// results.push({ attempt, altitude: Number(data) });
|
||||
// }
|
||||
// const graph = results.map(r => {
|
||||
// if (r.altitude > 0) {
|
||||
// return ".".repeat(Math.min(50, Math.floor(r.altitude / 200))) + "*";
|
||||
// }
|
||||
// return "-".repeat(Math.min(50, Math.floor(-r.altitude / 200)));
|
||||
// }).join("\n");
|
||||
//
|
||||
// console.log(graph);
|
||||
|
||||
const correctResult = getAuthResult(kingOfTheHillServer, `${password}`, 1);
|
||||
expect(correctResult.response.code).toBe(ResponseCodeEnum.Success);
|
||||
});
|
||||
});
|
||||
|
||||
describe("mutateDarknet and webstorm", () => {
|
||||
test("mutateDarknet", () => {
|
||||
const spiedExceptionAlert = jest.spyOn(exceptionAlertModule, "exceptionAlert");
|
||||
const spiedConsoleError = jest.spyOn(console, "error").mockImplementation();
|
||||
for (let i = 0; i < 5000; ++i) {
|
||||
mutateDarknet();
|
||||
}
|
||||
expect(spiedExceptionAlert).not.toHaveBeenCalled();
|
||||
expect(spiedConsoleError).not.toHaveBeenCalled();
|
||||
});
|
||||
test("webstorm", async () => {
|
||||
const spiedExceptionAlert = jest.spyOn(exceptionAlertModule, "exceptionAlert");
|
||||
const spiedConsoleError = jest.spyOn(console, "error").mockImplementation();
|
||||
jest.spyOn(UtilityModule, "sleep").mockImplementation();
|
||||
for (let i = 0; i < 100; ++i) {
|
||||
await launchWebstorm();
|
||||
}
|
||||
expect(spiedExceptionAlert).not.toHaveBeenCalled();
|
||||
expect(spiedConsoleError).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
function validatePath(hostname: string): void {
|
||||
expectWithMessage(isDirectoryPath(`${hostname}/`), true, `Invalid hostname: ${hostname}`);
|
||||
expectWithMessage(isFilePath(`${hostname}/data.txt`), true, `Invalid hostname: ${hostname}`);
|
||||
}
|
||||
|
||||
describe("Darknet server name generator", () => {
|
||||
test("Base name and l33t", () => {
|
||||
for (const name of commonPasswordDictionary) {
|
||||
validatePath(name);
|
||||
}
|
||||
for (const name of loreNames) {
|
||||
validatePath(name);
|
||||
}
|
||||
for (const name of presetNames) {
|
||||
validatePath(name);
|
||||
}
|
||||
for (const prefix of ServerNamePrefixes) {
|
||||
for (const connector of connectors) {
|
||||
for (const suffix of ServerNameSuffixes) {
|
||||
validatePath(`${prefix}${connector}${suffix}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const name of Object.values(l33t)) {
|
||||
validatePath(name);
|
||||
}
|
||||
});
|
||||
test("generateDarknetServerName", () => {
|
||||
DarknetState.offlineServers = [];
|
||||
for (let i = 0; i < 1000; ++i) {
|
||||
validatePath(generateDarknetServerName());
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("Cache filename generator", () => {
|
||||
test("Random prefix", () => {
|
||||
for (let i = 0; i < 10000; ++i) {
|
||||
const cacheFilename = generateCacheFilename();
|
||||
if (!cacheFilename) {
|
||||
throw new Error("Invalid cache filename");
|
||||
}
|
||||
validatePath(cacheFilename);
|
||||
}
|
||||
});
|
||||
test("Cache file in labyrinth server", () => {
|
||||
const cacheFilename = generateCacheFilename(LAB_CACHE_NAME);
|
||||
if (!cacheFilename) {
|
||||
throw new Error("Invalid cache filename");
|
||||
}
|
||||
validatePath(cacheFilename);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Clue filename generator", () => {
|
||||
test("getClueFileName", () => {
|
||||
for (let i = 0; i < 10000; ++i) {
|
||||
expect(() => {
|
||||
getClueFileName(passwordFileNames);
|
||||
getClueFileName(notebookFileNames);
|
||||
}).not.toThrow();
|
||||
}
|
||||
});
|
||||
});
|
||||
308
test/jest/Darknet/Labyrinth.test.ts
Normal file
308
test/jest/Darknet/Labyrinth.test.ts
Normal file
@@ -0,0 +1,308 @@
|
||||
import {
|
||||
generateMaze,
|
||||
getLabAugReward,
|
||||
getLabMaze,
|
||||
getLabyrinthDetails,
|
||||
getSurroundingsVisualized,
|
||||
handleLabyrinthPassword,
|
||||
labData,
|
||||
} from "../../../src/DarkNet/effects/labyrinth";
|
||||
import { initGameEnvironment, setupBasicTestingEnvironment } from "../Utilities";
|
||||
import { getDarkscapeNavigator } from "../../../src/DarkNet/effects/effects";
|
||||
import { Player } from "@player";
|
||||
import { DarknetState } from "../../../src/DarkNet/models/DarknetState";
|
||||
import { populateDarknet } from "../../../src/DarkNet/controllers/NetworkGenerator";
|
||||
import { SpecialServers } from "../../../src/Server/data/SpecialServers";
|
||||
import { MAX_NET_DEPTH, NET_WIDTH } from "../../../src/DarkNet/Enums";
|
||||
import type { DarknetServer } from "../../../src/Server/DarknetServer";
|
||||
import { PlayerOwnedAugmentation } from "../../../src/Augmentation/PlayerOwnedAugmentation";
|
||||
import { AugmentationName } from "@enums";
|
||||
import { getAuthResult } from "../../../src/DarkNet/effects/authentication";
|
||||
import { getMostRecentAuthLog } from "../../../src/DarkNet/models/packetSniffing";
|
||||
|
||||
beforeAll(() => {
|
||||
initGameEnvironment();
|
||||
setupBasicTestingEnvironment({ purchasePServer: true, purchaseHacknetServer: true });
|
||||
getDarkscapeNavigator();
|
||||
Player.gainCharismaExp(1e100);
|
||||
});
|
||||
|
||||
const setupBN15Environment = (labAugCount: number) => {
|
||||
Player.bitNodeN = 15;
|
||||
const augs = [
|
||||
AugmentationName.TheBrokenWings,
|
||||
AugmentationName.TheBoots,
|
||||
AugmentationName.TheHammer,
|
||||
AugmentationName.TheRedPill,
|
||||
AugmentationName.TheLaw,
|
||||
AugmentationName.TheSword,
|
||||
AugmentationName.NeuroFluxGovernor,
|
||||
];
|
||||
|
||||
Player.augmentations = augs.slice(0, labAugCount).map((aug) => new PlayerOwnedAugmentation(aug));
|
||||
|
||||
DarknetState.Network = new Array(MAX_NET_DEPTH)
|
||||
.fill(null)
|
||||
.map(() => new Array<DarknetServer | null>(NET_WIDTH).fill(null));
|
||||
populateDarknet();
|
||||
};
|
||||
|
||||
const setupNonBN15Environment = (labAugCount: number, hasSf15 = false, allowTRPInLab = true) => {
|
||||
Player.sourceFiles.set(15, hasSf15 ? 1 : 0);
|
||||
Player.bitNodeN = allowTRPInLab ? 1 : 8;
|
||||
const augs = [
|
||||
AugmentationName.TheBrokenWings,
|
||||
AugmentationName.TheBoots,
|
||||
AugmentationName.TheHammer,
|
||||
AugmentationName.TheLaw,
|
||||
AugmentationName.TheSword,
|
||||
AugmentationName.TheRedPill,
|
||||
AugmentationName.NeuroFluxGovernor,
|
||||
];
|
||||
|
||||
for (let i = 0; i < labAugCount; i++) {
|
||||
Player.augmentations.push(new PlayerOwnedAugmentation(augs[i]));
|
||||
}
|
||||
|
||||
DarknetState.Network = new Array(MAX_NET_DEPTH)
|
||||
.fill(null)
|
||||
.map(() => new Array<DarknetServer | null>(NET_WIDTH).fill(null));
|
||||
populateDarknet();
|
||||
};
|
||||
|
||||
describe("Labyrinth Tests", () => {
|
||||
it("should create a maze with the correct size", () => {
|
||||
const width = 30;
|
||||
const height = 20;
|
||||
const maze = generateMaze(width, height);
|
||||
|
||||
// console.log(
|
||||
// maze
|
||||
// .map((row) =>
|
||||
// row
|
||||
// .split("")
|
||||
// .map((x) => `${x}${x}`)
|
||||
// .join(""),
|
||||
// )
|
||||
// .join("\n"),
|
||||
// );
|
||||
|
||||
// console.log(getSurroundingsVisualized(maze, 1, 1));
|
||||
|
||||
expect(maze).toHaveLength(height + 1);
|
||||
expect(maze[0]).toHaveLength(width - 1);
|
||||
});
|
||||
|
||||
it("should accept basic commands", () => {
|
||||
setupBN15Environment(0);
|
||||
const labDetails = getLabyrinthDetails();
|
||||
|
||||
expect(labDetails.lab).not.toBeNull();
|
||||
if (labDetails.lab === null) return;
|
||||
|
||||
const result = handleLabyrinthPassword("go north", labDetails.lab, -1);
|
||||
expect(result.message).toBe("You cannot go that way. You are still at 1,1.");
|
||||
|
||||
const surroundings = result.data;
|
||||
const mazeData = getLabMaze();
|
||||
const mazeSurroundings = mazeData
|
||||
.slice(0, 3)
|
||||
.map((row) => row.slice(0, 3))
|
||||
.join("\n");
|
||||
// Add the player icon at (1,1)
|
||||
const expectedSurroundings = mazeSurroundings.slice(0, 5) + "@" + mazeSurroundings.slice(6, 11);
|
||||
expect(surroundings).toEqual(expectedSurroundings);
|
||||
});
|
||||
|
||||
it("should give location for bad commands", () => {
|
||||
setupBN15Environment(0);
|
||||
const labDetails = getLabyrinthDetails();
|
||||
|
||||
expect(labDetails.lab).not.toBeNull();
|
||||
if (labDetails.lab === null) return;
|
||||
|
||||
const result = handleLabyrinthPassword("1234", labDetails.lab, -1);
|
||||
expect(result.message).toBe(`You don't know how to do that. Try a command such as "go north"`);
|
||||
|
||||
const surroundings = result.data;
|
||||
const mazeData = getLabMaze();
|
||||
const mazeSurroundings = mazeData
|
||||
.slice(0, 3)
|
||||
.map((row) => row.slice(0, 3))
|
||||
.join("\n");
|
||||
// Add the player icon at (1,1)
|
||||
const expectedSurroundings = mazeSurroundings.slice(0, 5) + "@" + mazeSurroundings.slice(6, 11);
|
||||
expect(surroundings).toEqual(expectedSurroundings);
|
||||
});
|
||||
|
||||
it("should give the new location after moving and log it", () => {
|
||||
setupBN15Environment(0);
|
||||
const labDetails = getLabyrinthDetails();
|
||||
|
||||
expect(labDetails.lab).not.toBeNull();
|
||||
if (labDetails.lab === null) return;
|
||||
const mazeData = getLabMaze();
|
||||
const direction = mazeData[1][2] === " " ? "east" : "south";
|
||||
const [newX, newY] = direction === "east" ? [3, 1] : [1, 3];
|
||||
|
||||
const result = getAuthResult(labDetails.lab, direction);
|
||||
expect(result.response.message).toBe(`You have moved to ${newX},${newY}.`);
|
||||
|
||||
const surroundings = result.response.data;
|
||||
expect(surroundings).toEqual(getSurroundingsVisualized(mazeData, newX, newY, 1, true, true));
|
||||
|
||||
const log = getMostRecentAuthLog(labDetails.name);
|
||||
expect(log).not.toBeNull();
|
||||
if (log === null) return;
|
||||
expect(log.message).toEqual(result.response.message);
|
||||
expect(log.data).toEqual(surroundings);
|
||||
});
|
||||
|
||||
describe("non-bitnode 15 lab tests", () => {
|
||||
it("should not attach a lab if the player does not have SF15 access", () => {
|
||||
setupNonBN15Environment(0, false);
|
||||
const labDetails = getLabyrinthDetails();
|
||||
|
||||
expect(labDetails.name).toEqual("");
|
||||
expect(labDetails.lab).toBeNull();
|
||||
});
|
||||
it("should attach normal lab if the player has SF15 access and no lab augs", () => {
|
||||
setupNonBN15Environment(0, true);
|
||||
const labDetails = getLabyrinthDetails();
|
||||
|
||||
expect(labDetails.name).toEqual(SpecialServers.NormalLab);
|
||||
expect(labDetails.lab?.hostname).toEqual(SpecialServers.NormalLab);
|
||||
expect(labDetails.lab?.requiredCharismaSkill).toEqual(labData[SpecialServers.NormalLab].cha);
|
||||
expect(getLabAugReward()).toEqual(AugmentationName.TheBrokenWings);
|
||||
});
|
||||
it("should attach cruel lab if the player has SF15 access and normal lab aug", () => {
|
||||
setupNonBN15Environment(1, true);
|
||||
const labDetails = getLabyrinthDetails();
|
||||
|
||||
expect(labDetails.name).toEqual(SpecialServers.CruelLab);
|
||||
expect(labDetails.lab?.hostname).toEqual(SpecialServers.CruelLab);
|
||||
expect(labDetails.lab?.requiredCharismaSkill).toEqual(labData[SpecialServers.CruelLab].cha);
|
||||
expect(getLabAugReward()).toEqual(AugmentationName.TheBoots);
|
||||
});
|
||||
it("should attach merciless lab if the player has SF15 access and cruel lab aug", () => {
|
||||
setupNonBN15Environment(2, true);
|
||||
const labDetails = getLabyrinthDetails();
|
||||
|
||||
expect(labDetails.name).toEqual(SpecialServers.MercilessLab);
|
||||
expect(labDetails.lab?.hostname).toEqual(SpecialServers.MercilessLab);
|
||||
expect(labDetails.lab?.requiredCharismaSkill).toEqual(labData[SpecialServers.MercilessLab].cha);
|
||||
expect(getLabAugReward()).toEqual(AugmentationName.TheHammer);
|
||||
});
|
||||
it("should attach uber lab if the player has SF15 access and merciless lab aug", () => {
|
||||
setupNonBN15Environment(3, true);
|
||||
const labDetails = getLabyrinthDetails();
|
||||
|
||||
expect(labDetails.name).toEqual(SpecialServers.UberLab);
|
||||
expect(labDetails.lab?.hostname).toEqual(SpecialServers.UberLab);
|
||||
expect(labDetails.lab?.requiredCharismaSkill).toEqual(labData[SpecialServers.UberLab].cha);
|
||||
expect(getLabAugReward()).toEqual(AugmentationName.TheLaw);
|
||||
});
|
||||
it("should attach eternal lab if the player has SF15 access and uber lab aug", () => {
|
||||
setupNonBN15Environment(4, true);
|
||||
const labDetails = getLabyrinthDetails();
|
||||
|
||||
expect(labDetails.name).toEqual(SpecialServers.EternalLab);
|
||||
expect(labDetails.lab?.hostname).toEqual(SpecialServers.EternalLab);
|
||||
expect(labDetails.lab?.requiredCharismaSkill).toEqual(labData[SpecialServers.EternalLab].cha);
|
||||
expect(getLabAugReward()).toEqual(AugmentationName.TheSword);
|
||||
});
|
||||
it("should attach bonus lab, but not offer TRP, if the player has SF15 access and eternal lab aug, but the bitnode disables TRP in lab", () => {
|
||||
setupNonBN15Environment(5, true, false);
|
||||
const labDetails = getLabyrinthDetails();
|
||||
|
||||
expect(labDetails.name).toEqual(SpecialServers.BonusLab);
|
||||
expect(labDetails.lab?.hostname).toEqual(SpecialServers.BonusLab);
|
||||
expect(labDetails.lab?.requiredCharismaSkill).toEqual(labData[SpecialServers.BonusLab].cha);
|
||||
expect(getLabAugReward()).toEqual(AugmentationName.NeuroFluxGovernor);
|
||||
});
|
||||
it("should attach bonus lab if the player has SF15 access and final lab aug", () => {
|
||||
setupNonBN15Environment(5, true, true);
|
||||
const labDetails = getLabyrinthDetails();
|
||||
|
||||
expect(labDetails.name).toEqual(SpecialServers.FinalLab);
|
||||
expect(labDetails.lab?.hostname).toEqual(SpecialServers.FinalLab);
|
||||
expect(labDetails.lab?.requiredCharismaSkill).toEqual(labData[SpecialServers.FinalLab].cha);
|
||||
expect(getLabAugReward()).toEqual(AugmentationName.TheRedPill);
|
||||
});
|
||||
it("should attach bonus lab if the player has SF15 access and final lab aug", () => {
|
||||
setupNonBN15Environment(6, true, true);
|
||||
const labDetails = getLabyrinthDetails();
|
||||
|
||||
expect(labDetails.name).toEqual(SpecialServers.BonusLab);
|
||||
expect(labDetails.lab?.hostname).toEqual(SpecialServers.BonusLab);
|
||||
expect(labDetails.lab?.requiredCharismaSkill).toEqual(labData[SpecialServers.BonusLab].cha);
|
||||
expect(getLabAugReward()).toEqual(AugmentationName.NeuroFluxGovernor);
|
||||
});
|
||||
});
|
||||
|
||||
describe("bitnode 15 lab tests", () => {
|
||||
it("should attach normal lab if the player has SF15 access and no lab augs", () => {
|
||||
setupBN15Environment(0);
|
||||
const labDetails = getLabyrinthDetails();
|
||||
|
||||
expect(labDetails.name).toEqual(SpecialServers.NormalLab);
|
||||
expect(labDetails.lab?.hostname).toEqual(SpecialServers.NormalLab);
|
||||
expect(labDetails.lab?.requiredCharismaSkill).toEqual(labData[SpecialServers.NormalLab].cha);
|
||||
expect(getLabAugReward()).toEqual(AugmentationName.TheBrokenWings);
|
||||
});
|
||||
it("should attach cruel lab if the player has SF15 access and normal lab aug", () => {
|
||||
setupBN15Environment(1);
|
||||
const labDetails = getLabyrinthDetails();
|
||||
|
||||
expect(labDetails.name).toEqual(SpecialServers.CruelLab);
|
||||
expect(labDetails.lab?.hostname).toEqual(SpecialServers.CruelLab);
|
||||
expect(labDetails.lab?.requiredCharismaSkill).toEqual(labData[SpecialServers.CruelLab].cha);
|
||||
expect(getLabAugReward()).toEqual(AugmentationName.TheBoots);
|
||||
});
|
||||
it("should attach merciless lab if the player has SF15 access and cruel lab aug", () => {
|
||||
setupBN15Environment(2);
|
||||
const labDetails = getLabyrinthDetails();
|
||||
|
||||
expect(labDetails.name).toEqual(SpecialServers.MercilessLab);
|
||||
expect(labDetails.lab?.hostname).toEqual(SpecialServers.MercilessLab);
|
||||
expect(labDetails.lab?.requiredCharismaSkill).toEqual(labData[SpecialServers.MercilessLab].cha);
|
||||
expect(getLabAugReward()).toEqual(AugmentationName.TheHammer);
|
||||
});
|
||||
it("should attach uber lab if the player has SF15 access and merciless lab aug", () => {
|
||||
setupBN15Environment(3);
|
||||
const labDetails = getLabyrinthDetails();
|
||||
|
||||
expect(labDetails.name).toEqual(SpecialServers.UberLab);
|
||||
expect(labDetails.lab?.hostname).toEqual(SpecialServers.UberLab);
|
||||
expect(labDetails.lab?.requiredCharismaSkill).toEqual(labData[SpecialServers.UberLab].cha);
|
||||
expect(getLabAugReward()).toEqual(AugmentationName.TheRedPill);
|
||||
});
|
||||
it("should attach eternal lab if the player has SF15 access and uber lab aug", () => {
|
||||
setupBN15Environment(4);
|
||||
const labDetails = getLabyrinthDetails();
|
||||
|
||||
expect(labDetails.name).toEqual(SpecialServers.EternalLab);
|
||||
expect(labDetails.lab?.hostname).toEqual(SpecialServers.EternalLab);
|
||||
expect(labDetails.lab?.requiredCharismaSkill).toEqual(labData[SpecialServers.EternalLab].cha);
|
||||
expect(getLabAugReward()).toEqual(AugmentationName.TheLaw);
|
||||
});
|
||||
it("should attach final lab if the player has SF15 access and eternal lab aug", () => {
|
||||
setupBN15Environment(5);
|
||||
const labDetails = getLabyrinthDetails();
|
||||
|
||||
expect(labDetails.name).toEqual(SpecialServers.FinalLab);
|
||||
expect(labDetails.lab?.hostname).toEqual(SpecialServers.FinalLab);
|
||||
expect(labDetails.lab?.requiredCharismaSkill).toEqual(labData[SpecialServers.FinalLab].cha);
|
||||
expect(getLabAugReward()).toEqual(AugmentationName.TheSword);
|
||||
});
|
||||
it("should attach bonus lab if the player has SF15 access and final lab aug", () => {
|
||||
setupBN15Environment(6);
|
||||
const labDetails = getLabyrinthDetails();
|
||||
|
||||
expect(labDetails.name).toEqual(SpecialServers.BonusLab);
|
||||
expect(labDetails.lab?.hostname).toEqual(SpecialServers.BonusLab);
|
||||
expect(labDetails.lab?.requiredCharismaSkill).toEqual(labData[SpecialServers.BonusLab].cha);
|
||||
expect(getLabAugReward()).toEqual(AugmentationName.NeuroFluxGovernor);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -20,7 +20,27 @@ describe("v3", () => {
|
||||
|
||||
const mockedDownload = jest.spyOn(FileUtils, "downloadContentAsFile");
|
||||
|
||||
const originalConsoleError = console.error;
|
||||
const originalConsoleWarning = console.warn;
|
||||
const consoleError = jest.spyOn(console, "error").mockImplementation((...data: unknown[]) => {
|
||||
if (Array.isArray(data) && data.length > 0 && (data[0] === "There was no Darknet savedata" || data[0] === "")) {
|
||||
return;
|
||||
}
|
||||
originalConsoleError(...data);
|
||||
});
|
||||
const consoleWarning = jest.spyOn(console, "warn").mockImplementation((...data: unknown[]) => {
|
||||
if (
|
||||
Array.isArray(data) &&
|
||||
data.length > 0 &&
|
||||
(data[0] === "Encountered the following issue while loading Darknet savedata:" || data[0] === "Savedata:")
|
||||
) {
|
||||
return;
|
||||
}
|
||||
originalConsoleWarning(...data);
|
||||
});
|
||||
await loadGame(await db.load());
|
||||
consoleError.mockRestore();
|
||||
consoleWarning.mockRestore();
|
||||
|
||||
// Check if auto-migration works
|
||||
expect(
|
||||
|
||||
1322
test/jest/Netscript/Darknet.test.ts
Normal file
1322
test/jest/Netscript/Darknet.test.ts
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,9 +1,9 @@
|
||||
import { installAugmentations } from "../../../src/Augmentation/AugmentationHelpers";
|
||||
import { blackOpsArray } from "../../../src/Bladeburner/data/BlackOperations";
|
||||
import { AugmentationName, CompanyName, FactionName, JobField, JobName } from "@enums";
|
||||
import { AugmentationName, CompanyName, CompletedProgramName, FactionName, JobField, JobName } from "@enums";
|
||||
import { Player } from "@player";
|
||||
import { prestigeSourceFile } from "../../../src/Prestige";
|
||||
import { GetServerOrThrow } from "../../../src/Server/AllServers";
|
||||
import { disconnectServers, GetServerOrThrow } from "../../../src/Server/AllServers";
|
||||
import { SpecialServers } from "../../../src/Server/data/SpecialServers";
|
||||
import { Factions } from "../../../src/Faction/Factions";
|
||||
import { PlayerOwnedAugmentation } from "../../../src/Augmentation/PlayerOwnedAugmentation";
|
||||
@@ -12,6 +12,8 @@ import { Terminal } from "../../../src/Terminal";
|
||||
import type { NSFull } from "../../../src/NetscriptFunctions";
|
||||
import { Companies } from "../../../src/Company/Companies";
|
||||
import { CompanyPositions } from "../../../src/Company/CompanyPositions";
|
||||
import { getTorRouter } from "../../../src/Server/ServerHelpers";
|
||||
import * as exceptionAlertModule from "../../../src/utils/helpers/exceptionAlert";
|
||||
|
||||
const nextBN = 3;
|
||||
|
||||
@@ -446,3 +448,73 @@ describe("applyToCompany", () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("purchaseProgram", () => {
|
||||
beforeEach(() => {
|
||||
setupBasicTestingEnvironment();
|
||||
prestigeSourceFile(true);
|
||||
Player.money = 1e15;
|
||||
getTorRouter();
|
||||
});
|
||||
|
||||
describe("Success", () => {
|
||||
beforeEach(() => {
|
||||
const ns = getNS();
|
||||
expect(ns.singularity.purchaseTor()).toStrictEqual(true);
|
||||
});
|
||||
test("return true if already bought", () => {
|
||||
const ns = getNS();
|
||||
expect(Player.hasProgram(CompletedProgramName.bruteSsh)).toStrictEqual(false);
|
||||
Player.getHomeComputer().pushProgram(CompletedProgramName.bruteSsh);
|
||||
expect(Player.hasProgram(CompletedProgramName.bruteSsh)).toStrictEqual(true);
|
||||
expect(ns.singularity.purchaseProgram(CompletedProgramName.bruteSsh)).toStrictEqual(true);
|
||||
expect(Player.hasProgram(CompletedProgramName.bruteSsh)).toStrictEqual(true);
|
||||
});
|
||||
test("bruteSsh", () => {
|
||||
const ns = getNS();
|
||||
expect(Player.hasProgram(CompletedProgramName.bruteSsh)).toStrictEqual(false);
|
||||
expect(ns.singularity.purchaseProgram(CompletedProgramName.bruteSsh)).toStrictEqual(true);
|
||||
expect(Player.hasProgram(CompletedProgramName.bruteSsh)).toStrictEqual(true);
|
||||
});
|
||||
test("darkscape", () => {
|
||||
const spiedExceptionAlert = jest.spyOn(exceptionAlertModule, "exceptionAlert");
|
||||
const ns = getNS();
|
||||
expect(Player.hasProgram(CompletedProgramName.darkscape)).toStrictEqual(false);
|
||||
expect(ns.singularity.purchaseProgram(CompletedProgramName.darkscape)).toStrictEqual(true);
|
||||
expect(Player.hasProgram(CompletedProgramName.darkscape)).toStrictEqual(true);
|
||||
expect(spiedExceptionAlert).not.toHaveBeenCalled();
|
||||
});
|
||||
test("darkscape with lowercase program name", () => {
|
||||
const spiedExceptionAlert = jest.spyOn(exceptionAlertModule, "exceptionAlert");
|
||||
const ns = getNS();
|
||||
expect(Player.hasProgram(CompletedProgramName.darkscape)).toStrictEqual(false);
|
||||
expect(ns.singularity.purchaseProgram(CompletedProgramName.darkscape.toLowerCase())).toStrictEqual(true);
|
||||
expect(Player.hasProgram(CompletedProgramName.darkscape)).toStrictEqual(true);
|
||||
expect(spiedExceptionAlert).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("Failure", () => {
|
||||
test("No TOR", () => {
|
||||
// Remove TOR router
|
||||
disconnectServers(Player.getHomeComputer(), GetServerOrThrow(SpecialServers.DarkWeb));
|
||||
|
||||
const ns = getNS();
|
||||
expect(Player.hasTorRouter()).toStrictEqual(false);
|
||||
expect(Player.hasProgram(CompletedProgramName.bruteSsh)).toStrictEqual(false);
|
||||
expect(ns.singularity.purchaseProgram(CompletedProgramName.bruteSsh)).toStrictEqual(false);
|
||||
expect(Player.hasProgram(CompletedProgramName.bruteSsh)).toStrictEqual(false);
|
||||
});
|
||||
test("Invalid program name", () => {
|
||||
const ns = getNS();
|
||||
expect(ns.singularity.purchaseProgram("InvalidProgram.exe")).toStrictEqual(false);
|
||||
});
|
||||
test("Not enough money", () => {
|
||||
const ns = getNS();
|
||||
Player.money = 0;
|
||||
expect(Player.hasProgram(CompletedProgramName.bruteSsh)).toStrictEqual(false);
|
||||
expect(ns.singularity.purchaseProgram(CompletedProgramName.bruteSsh)).toStrictEqual(false);
|
||||
expect(Player.hasProgram(CompletedProgramName.bruteSsh)).toStrictEqual(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -68,6 +68,7 @@ describe("getTabCompletionPossibilities", function () {
|
||||
expect(options.sort()).toEqual(
|
||||
[
|
||||
"BruteSSH.exe",
|
||||
"DarkscapeNavigator.exe",
|
||||
"FTPCrack.exe",
|
||||
"relaySMTP.exe",
|
||||
"HTTPWorm.exe",
|
||||
|
||||
@@ -4,13 +4,17 @@ import type { ScriptFilePath } from "../../src/Paths/ScriptFilePath";
|
||||
import { PlayerObject } from "../../src/PersonObjects/Player/PlayerObject";
|
||||
import { Player, setPlayer } from "../../src/Player";
|
||||
import { RunningScript } from "../../src/Script/RunningScript";
|
||||
import { GetServerOrThrow, initForeignServers, prestigeAllServers } from "../../src/Server/AllServers";
|
||||
import { GetServerOrThrow, prestigeAllServers } from "../../src/Server/AllServers";
|
||||
import { SpecialServers } from "../../src/Server/data/SpecialServers";
|
||||
import { initSourceFiles } from "../../src/SourceFile/SourceFiles";
|
||||
import { FormatsNeedToChange } from "../../src/ui/formatNumber";
|
||||
import { Router } from "../../src/ui/GameRoot";
|
||||
import { config } from "../../src/NetscriptJSEvaluator";
|
||||
import type { NetscriptContext } from "../../src/Netscript/APIWrapper";
|
||||
import { purchaseServer } from "../../src/Server/ServerPurchases";
|
||||
import { purchaseHacknet } from "../../src/Hacknet/HacknetHelpers";
|
||||
import { initForeignServers } from "../../src/Server/ServerHelpers";
|
||||
import { generateNextPid } from "../../src/Netscript/Pid";
|
||||
|
||||
declare const importActual: (typeof config)["doImport"];
|
||||
|
||||
@@ -47,16 +51,29 @@ export function initGameEnvironment() {
|
||||
initSourceFiles();
|
||||
}
|
||||
|
||||
export function setupBasicTestingEnvironment(): void {
|
||||
export function setupBasicTestingEnvironment(
|
||||
{ purchaseHacknetServer, purchasePServer } = { purchasePServer: false, purchaseHacknetServer: false },
|
||||
): void {
|
||||
prestigeAllServers();
|
||||
setPlayer(new PlayerObject());
|
||||
Player.init();
|
||||
Player.sourceFiles.set(4, 3);
|
||||
Player.money = 1e15;
|
||||
initForeignServers(Player.getHomeComputer());
|
||||
if (purchasePServer) {
|
||||
purchaseServer("test-server-1", 2);
|
||||
}
|
||||
if (purchaseHacknetServer) {
|
||||
Player.sourceFiles.set(9, 3);
|
||||
purchaseHacknet();
|
||||
}
|
||||
}
|
||||
|
||||
export function getNS(): NSFull {
|
||||
const home = GetServerOrThrow(SpecialServers.Home);
|
||||
export function getWorkerScriptAndNS(hostname: string = SpecialServers.Home): {
|
||||
ws: WorkerScript;
|
||||
ns: NSFull;
|
||||
} {
|
||||
const home = GetServerOrThrow(hostname);
|
||||
home.maxRam = 1024;
|
||||
const filePath = "test.js" as ScriptFilePath;
|
||||
home.writeToScriptFile(filePath, "");
|
||||
@@ -65,12 +82,19 @@ export function getNS(): NSFull {
|
||||
throw new Error("Invalid script");
|
||||
}
|
||||
const runningScript = new RunningScript(script, 1024);
|
||||
const workerScript = new WorkerScript(runningScript, 1, NetscriptFunctions);
|
||||
const workerScript = new WorkerScript(runningScript, generateNextPid(), NetscriptFunctions);
|
||||
const ns = workerScript.env.vars;
|
||||
if (!ns) {
|
||||
throw new Error("Invalid NS instance");
|
||||
}
|
||||
return ns;
|
||||
return {
|
||||
ws: workerScript,
|
||||
ns,
|
||||
};
|
||||
}
|
||||
|
||||
export function getNS(hostname: string = SpecialServers.Home): NSFull {
|
||||
return getWorkerScriptAndNS(hostname).ns;
|
||||
}
|
||||
|
||||
export function getMockedNetscriptContext(
|
||||
@@ -87,3 +111,12 @@ export function getMockedNetscriptContext(
|
||||
} as unknown as WorkerScript,
|
||||
};
|
||||
}
|
||||
|
||||
// WIP: Improve this function to get a better stack trace or use jest-expect-message
|
||||
export function expectWithMessage(actual: unknown, expected: unknown, customMessage: string) {
|
||||
try {
|
||||
expect(actual).toStrictEqual(expected);
|
||||
} catch (error) {
|
||||
throw new Error(customMessage, { cause: error });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -459,6 +459,7 @@ exports[`Check Save File Continuity PlayerSave continuity 1`] = `
|
||||
"codingcontract": 0,
|
||||
"corporation": 0,
|
||||
"crime": 0,
|
||||
"darknet": 0,
|
||||
"gang": 0,
|
||||
"gang_expenses": 0,
|
||||
"hacking": 0,
|
||||
@@ -484,6 +485,7 @@ exports[`Check Save File Continuity PlayerSave continuity 1`] = `
|
||||
"codingcontract": 0,
|
||||
"corporation": 0,
|
||||
"crime": 0,
|
||||
"darknet": 0,
|
||||
"gang": 0,
|
||||
"gang_expenses": 0,
|
||||
"hacking": 0,
|
||||
|
||||
Reference in New Issue
Block a user