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:
Michael Ficocelli
2026-02-03 06:40:36 -05:00
committed by GitHub
parent a674633f6c
commit 6073964768
225 changed files with 15010 additions and 526 deletions

View 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();
}
});
});

View 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);
});
});
});

View File

@@ -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(

File diff suppressed because it is too large Load Diff

View File

@@ -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);
});
});
});

View File

@@ -68,6 +68,7 @@ describe("getTabCompletionPossibilities", function () {
expect(options.sort()).toEqual(
[
"BruteSSH.exe",
"DarkscapeNavigator.exe",
"FTPCrack.exe",
"relaySMTP.exe",
"HTTPWorm.exe",

View File

@@ -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 });
}
}

View File

@@ -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,