From 54f2830365ec82a3eb2523a3801e3a82241ccfb2 Mon Sep 17 00:00:00 2001 From: CatLover <152669316+catloversg@users.noreply.github.com> Date: Tue, 19 Nov 2024 12:21:17 +0700 Subject: [PATCH 1/5] CODEBASE: Validate AllGangs and StockMarket after loading with JSON.parse --- package-lock.json | 269 ++++++++---------- package.json | 1 + src/Gang/AllGangs.ts | 25 +- src/JsonSchema/Data/AllGangsSchema.ts | 42 +++ src/JsonSchema/Data/StockMarketSchema.ts | 138 +++++++++ src/JsonSchema/JsonSchemaValidator.ts | 10 + src/StockMarket/StockMarket.tsx | 60 ++-- test/jest/JsonSchema/AllGangsSchema.test.ts | 41 +++ .../jest/JsonSchema/StockMarketSchema.test.ts | 181 ++++++++++++ 9 files changed, 597 insertions(+), 170 deletions(-) create mode 100644 src/JsonSchema/Data/AllGangsSchema.ts create mode 100644 src/JsonSchema/Data/StockMarketSchema.ts create mode 100644 src/JsonSchema/JsonSchemaValidator.ts create mode 100644 test/jest/JsonSchema/AllGangsSchema.test.ts create mode 100644 test/jest/JsonSchema/StockMarketSchema.test.ts diff --git a/package-lock.json b/package-lock.json index ea667f06c8..10dda1e66c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,6 +24,7 @@ "acorn": "^8.11.3", "acorn-jsx-walk": "^2.0.0", "acorn-walk": "^8.3.2", + "ajv": "^8.17.1", "arg": "^5.0.2", "bcryptjs": "^2.4.3", "better-react-mathjax": "^2.0.3", @@ -2348,6 +2349,23 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/@eslint/eslintrc/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, "node_modules/@eslint/eslintrc/node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -2381,6 +2399,13 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/@eslint/eslintrc/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, "node_modules/@eslint/eslintrc/node_modules/type-fest": { "version": "0.20.2", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", @@ -3525,6 +3550,30 @@ "resolve": "~1.19.0" } }, + "node_modules/@microsoft/api-extractor-model/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@microsoft/api-extractor-model/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, "node_modules/@microsoft/api-extractor-model/node_modules/resolve": { "version": "1.19.0", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.19.0.tgz", @@ -3665,12 +3714,6 @@ "node": ">=8" } }, - "node_modules/@microsoft/api-extractor/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true - }, "node_modules/@microsoft/api-extractor/node_modules/lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", @@ -3780,12 +3823,6 @@ "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/@microsoft/tsdoc-config/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true - }, "node_modules/@mui/base": { "version": "5.0.0-beta.18", "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-beta.18.tgz", @@ -5772,15 +5809,15 @@ } }, "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "license": "MIT", "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" }, "funding": { "type": "github", @@ -5804,37 +5841,6 @@ } } }, - "node_modules/ajv-formats/node_modules/ajv": { - "version": "8.12.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", - "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", - "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ajv-formats/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true - }, - "node_modules/ajv-keywords": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", - "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "dev": true, - "peerDependencies": { - "ajv": "^6.9.1" - } - }, "node_modules/ansi-escapes": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", @@ -6185,22 +6191,6 @@ "webpack": ">=5" } }, - "node_modules/babel-loader/node_modules/ajv": { - "version": "8.12.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", - "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", - "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, "node_modules/babel-loader/node_modules/ajv-keywords": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", @@ -6213,12 +6203,6 @@ "ajv": "^8.8.2" } }, - "node_modules/babel-loader/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true - }, "node_modules/babel-loader/node_modules/schema-utils": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.2.0.tgz", @@ -7153,22 +7137,6 @@ "webpack": "^5.1.0" } }, - "node_modules/copy-webpack-plugin/node_modules/ajv": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", - "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, "node_modules/copy-webpack-plugin/node_modules/ajv-keywords": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", @@ -7201,12 +7169,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/copy-webpack-plugin/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true - }, "node_modules/copy-webpack-plugin/node_modules/path-type": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-5.0.0.tgz", @@ -8566,6 +8528,23 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/eslint/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, "node_modules/eslint/node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -8673,6 +8652,13 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/eslint/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, "node_modules/eslint/node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -8970,8 +8956,7 @@ "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, "node_modules/fast-glob": { "version": "3.3.2", @@ -9016,8 +9001,7 @@ "node_modules/fast-uri": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.1.tgz", - "integrity": "sha512-MWipKbbYiYI0UC7cl8m/i/IWTqfC8YXsqjzybjddLsFjStroQzsHXkc73JutMvBiXmOvapk+axIl79ig5t55Bw==", - "dev": true + "integrity": "sha512-MWipKbbYiYI0UC7cl8m/i/IWTqfC8YXsqjzybjddLsFjStroQzsHXkc73JutMvBiXmOvapk+axIl79ig5t55Bw==" }, "node_modules/fastest-levenshtein": { "version": "1.0.16", @@ -12964,10 +12948,10 @@ "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==" }, "node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" }, "node_modules/json-stable-stringify-without-jsonify": { "version": "1.0.1", @@ -16216,7 +16200,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -16465,6 +16448,40 @@ "url": "https://opencollective.com/webpack" } }, + "node_modules/schema-utils/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/schema-utils/node_modules/ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "ajv": "^6.9.1" + } + }, + "node_modules/schema-utils/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, "node_modules/select-hose": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", @@ -18213,22 +18230,6 @@ "webpack": "^4.0.0 || ^5.0.0" } }, - "node_modules/webpack-dev-middleware/node_modules/ajv": { - "version": "8.12.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", - "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", - "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, "node_modules/webpack-dev-middleware/node_modules/ajv-keywords": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", @@ -18241,12 +18242,6 @@ "ajv": "^8.8.2" } }, - "node_modules/webpack-dev-middleware/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true - }, "node_modules/webpack-dev-middleware/node_modules/schema-utils": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.2.0.tgz", @@ -18325,22 +18320,6 @@ } } }, - "node_modules/webpack-dev-server/node_modules/ajv": { - "version": "8.12.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", - "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", - "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, "node_modules/webpack-dev-server/node_modules/ajv-keywords": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", @@ -18353,12 +18332,6 @@ "ajv": "^8.8.2" } }, - "node_modules/webpack-dev-server/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true - }, "node_modules/webpack-dev-server/node_modules/schema-utils": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.2.0.tgz", diff --git a/package.json b/package.json index 848c488664..af250040c3 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,7 @@ "acorn": "^8.11.3", "acorn-jsx-walk": "^2.0.0", "acorn-walk": "^8.3.2", + "ajv": "^8.17.1", "arg": "^5.0.2", "bcryptjs": "^2.4.3", "better-react-mathjax": "^2.0.3", diff --git a/src/Gang/AllGangs.ts b/src/Gang/AllGangs.ts index 68b03c6aad..c2569b6198 100644 --- a/src/Gang/AllGangs.ts +++ b/src/Gang/AllGangs.ts @@ -1,12 +1,14 @@ import { FactionName } from "@enums"; import { Reviver } from "../utils/JSONReviver"; +import { JsonSchemaValidator } from "../JsonSchema/JsonSchemaValidator"; +import { dialogBoxCreate } from "../ui/React/DialogBox"; interface GangTerritory { power: number; territory: number; } -function getDefaultAllGangs() { +export function getDefaultAllGangs() { return { [FactionName.SlumSnakes]: { power: 1, @@ -46,7 +48,26 @@ export function resetGangs(): void { } export function loadAllGangs(saveString: string): void { - AllGangs = JSON.parse(saveString, Reviver); + let allGangsData: unknown; + let validate; + try { + allGangsData = JSON.parse(saveString, Reviver); + validate = JsonSchemaValidator.AllGangs; + if (!validate(allGangsData)) { + console.error("validate.errors:", validate.errors); + // validate.errors is an array of objects, so we need to use JSON.stringify. + throw new Error(JSON.stringify(validate.errors)); + } + } catch (error) { + console.error(error); + console.error("Invalid AllGangsSave:", saveString); + resetGangs(); + setTimeout(() => { + dialogBoxCreate(`Cannot load data of AllGangs. AllGangs is reset. Error: ${error}.`); + }, 1000); + return; + } + AllGangs = allGangsData; } export function getClashWinChance(thisGang: string, otherGang: string): number { diff --git a/src/JsonSchema/Data/AllGangsSchema.ts b/src/JsonSchema/Data/AllGangsSchema.ts new file mode 100644 index 0000000000..bc73caa23c --- /dev/null +++ b/src/JsonSchema/Data/AllGangsSchema.ts @@ -0,0 +1,42 @@ +import { JSONSchemaType } from "ajv"; +import type { AllGangs } from "../../Gang/AllGangs"; +import { FactionName } from "@enums"; + +export const AllGangsSchema: JSONSchemaType = { + $schema: "http://json-schema.org/draft-07/schema#", + type: "object", + patternProperties: { + ".*": { + type: "object", + properties: { + power: { + type: "number", + }, + territory: { + type: "number", + }, + }, + required: ["power", "territory"], + }, + }, + propertyNames: { + enum: [ + FactionName.SlumSnakes, + FactionName.Tetrads, + FactionName.TheSyndicate, + FactionName.TheDarkArmy, + FactionName.SpeakersForTheDead, + FactionName.NiteSec, + FactionName.TheBlackHand, + ], + }, + required: [ + FactionName.SlumSnakes, + FactionName.Tetrads, + FactionName.TheSyndicate, + FactionName.TheDarkArmy, + FactionName.SpeakersForTheDead, + FactionName.NiteSec, + FactionName.TheBlackHand, + ], +}; diff --git a/src/JsonSchema/Data/StockMarketSchema.ts b/src/JsonSchema/Data/StockMarketSchema.ts new file mode 100644 index 0000000000..0e08c5adb0 --- /dev/null +++ b/src/JsonSchema/Data/StockMarketSchema.ts @@ -0,0 +1,138 @@ +import { OrderType, PositionType, StockSymbol } from "@enums"; +import { getKeyList } from "../../utils/helpers/getKeyList"; +import { Stock } from "../../StockMarket/Stock"; +import { Order } from "../../StockMarket/Order"; + +const stockSymbolKeys = Object.keys(StockSymbol); +const stockSymbolValues = Object.values(StockSymbol); +const stockKeys = getKeyList(Stock); +const orderKeys = getKeyList(Order); + +/** + * It's intentional to not use JSONSchemaType here. The data structure of StockMarket is not suitable for the usage of + * JSONSchemaType. These are 2 biggest problems: + * - IStockMarket is an intersection type. In our case, satisfying TS's type-checking for JSONSchemaType is too hard. + * - Stock and Order are classes, not interfaces or types. In those classes, we have functions, but functions are not + * supported by JSON (without ugly hacks). Let's use the "Order" class as an example. The Order class has the toJSON + * function, so ajv forces us to define the "toJSON" property. If we define it, we have to give it a "type". In + * JavaScript, a function is just an object, so the naive solution is to use {type:"object"}. However, the generated + * validation code uses "typeof" to check the data. "typeof functionName" is "function", not "object", so ajv sees it as + * invalid data. There are some ways to work around this problem, but all of them are complicated. In the end, + * JSONSchemaType is only a utility type. If our schema is designed and tested properly, after the validation, we can + * typecast the data. + */ +export const StockMarketSchema = { + $schema: "http://json-schema.org/draft-07/schema#", + type: "object", + patternProperties: { + "^(?!.*(lastUpdate|Orders|storedCycles|ticksUntilCycle))": { + type: "object", + additionalProperties: false, + properties: { + b: { + type: "boolean", + }, + cap: { + type: "number", + }, + lastPrice: { + type: "number", + }, + maxShares: { + type: "number", + }, + mv: { + type: "number", + }, + name: { + type: "string", + enum: [...stockSymbolKeys], + }, + otlkMag: { + type: "number", + }, + otlkMagForecast: { + type: "number", + }, + playerAvgPx: { + type: "number", + }, + playerAvgShortPx: { + type: "number", + }, + playerShares: { + type: "number", + }, + playerShortShares: { + type: "number", + }, + price: { + type: "number", + }, + shareTxForMovement: { + type: "number", + }, + shareTxUntilMovement: { + type: "number", + }, + spreadPerc: { + type: "number", + }, + symbol: { + type: "string", + enum: [...stockSymbolValues], + }, + totalShares: { + type: "number", + }, + }, + required: [...stockKeys], + }, + }, + properties: { + lastUpdate: { type: "number" }, + Orders: { + type: "object", + patternProperties: { + ".*": { + type: "array", + items: { + type: "object", + additionalProperties: false, + properties: { + pos: { + type: "string", + enum: [PositionType.Long, PositionType.Short], + }, + price: { + type: "number", + }, + shares: { + type: "number", + }, + stockSymbol: { + type: "string", + enum: [...stockSymbolValues], + }, + type: { + type: "string", + enum: [OrderType.LimitBuy, OrderType.LimitSell, OrderType.StopBuy, OrderType.StopSell], + }, + }, + required: [...orderKeys], + }, + }, + }, + propertyNames: { + enum: [...stockSymbolValues], + }, + required: [], + }, + storedCycles: { type: "number" }, + ticksUntilCycle: { type: "number" }, + }, + propertyNames: { + enum: [...stockSymbolKeys, "lastUpdate", "Orders", "storedCycles", "ticksUntilCycle"], + }, + required: ["lastUpdate", "Orders", "storedCycles", "ticksUntilCycle"], +}; diff --git a/src/JsonSchema/JsonSchemaValidator.ts b/src/JsonSchema/JsonSchemaValidator.ts new file mode 100644 index 0000000000..9790b9781c --- /dev/null +++ b/src/JsonSchema/JsonSchemaValidator.ts @@ -0,0 +1,10 @@ +import Ajv from "ajv"; +import { AllGangsSchema } from "./Data/AllGangsSchema"; +import { StockMarketSchema } from "./Data/StockMarketSchema"; + +const ajv = new Ajv(); + +export const JsonSchemaValidator = { + AllGangs: ajv.compile(AllGangsSchema), + StockMarket: ajv.compile(StockMarketSchema), +}; diff --git a/src/StockMarket/StockMarket.tsx b/src/StockMarket/StockMarket.tsx index 97018bb8d7..d305ca55d6 100644 --- a/src/StockMarket/StockMarket.tsx +++ b/src/StockMarket/StockMarket.tsx @@ -16,13 +16,19 @@ import { Reviver } from "../utils/JSONReviver"; import { NetscriptContext } from "../Netscript/APIWrapper"; import { helpers } from "../Netscript/NetscriptHelpers"; import { getRandomIntInclusive } from "../utils/helpers/getRandomIntInclusive"; +import { JsonSchemaValidator } from "../JsonSchema/JsonSchemaValidator"; +import { Player } from "../Player"; -export let StockMarket: IStockMarket = { - lastUpdate: 0, - Orders: {}, - storedCycles: 0, - ticksUntilCycle: 0, -} as IStockMarket; // Maps full stock name -> Stock object +export function getDefaultEmptyStockMarket(): IStockMarket { + return { + lastUpdate: 0, + Orders: {}, + storedCycles: 0, + ticksUntilCycle: 0, + } as IStockMarket; +} + +export let StockMarket = getDefaultEmptyStockMarket(); // Maps full stock name -> Stock object // Gross type, needs to be addressed export const SymbolToStockMap: Record = {}; // Maps symbol -> Stock object @@ -130,27 +136,41 @@ export function cancelOrder(params: ICancelOrderParams, ctx?: NetscriptContext): } export function loadStockMarket(saveString: string): void { - if (saveString === "") { - StockMarket = { - lastUpdate: 0, - Orders: {}, - storedCycles: 0, - ticksUntilCycle: 0, - } as IStockMarket; - } else StockMarket = JSON.parse(saveString, Reviver); + let stockMarketData: unknown; + let validate; + try { + stockMarketData = JSON.parse(saveString, Reviver); + validate = JsonSchemaValidator.StockMarket; + if (!validate(stockMarketData)) { + console.error("validate.errors:", validate.errors); + // validate.errors is an array of objects, so we need to use JSON.stringify. + throw new Error(JSON.stringify(validate.errors)); + } + } catch (error) { + console.error(error); + console.error("Invalid StockMarketSave:", saveString); + deleteStockMarket(); + if (Player.hasWseAccount) { + initStockMarket(); + } + const errorMessage = `Cannot load data of StockMarket. StockMarket is reset.`; + setTimeout(() => { + dialogBoxCreate(errorMessage); + }, 1000); + return; + } + // Typecasting here is fine because we validated the loaded data. + StockMarket = stockMarketData as IStockMarket; } export function deleteStockMarket(): void { - StockMarket = { - lastUpdate: 0, - Orders: {}, - storedCycles: 0, - ticksUntilCycle: 0, - } as IStockMarket; + StockMarket = getDefaultEmptyStockMarket(); } export function initStockMarket(): void { + console.log(Object.getOwnPropertyNames(StockMarket)); for (const stockName of Object.getOwnPropertyNames(StockMarket)) { + console.log(stockName); // eslint-disable-next-line @typescript-eslint/no-dynamic-delete delete StockMarket[stockName]; } diff --git a/test/jest/JsonSchema/AllGangsSchema.test.ts b/test/jest/JsonSchema/AllGangsSchema.test.ts new file mode 100644 index 0000000000..667405c1fa --- /dev/null +++ b/test/jest/JsonSchema/AllGangsSchema.test.ts @@ -0,0 +1,41 @@ +import { getDefaultAllGangs } from "../../../src/Gang/AllGangs"; +import { JsonSchemaValidator } from "../../../src/JsonSchema/JsonSchemaValidator"; + +describe("Success", () => { + test("Default AllGangs", () => { + const defaultAllGangs = getDefaultAllGangs(); + expect(JsonSchemaValidator.AllGangs(defaultAllGangs)).toStrictEqual(true); + }); +}); + +describe("Failure", () => { + test("Do not have all gangs", () => { + const defaultAllGangs = getDefaultAllGangs() as Record; + delete defaultAllGangs["Slum Snakes"]; + expect(JsonSchemaValidator.AllGangs(defaultAllGangs)).toStrictEqual(false); + }); + test("Have an unexpected gang", () => { + const defaultAllGangs = getDefaultAllGangs() as Record; + defaultAllGangs["CyberSec"] = { + power: 1, + territory: 1 / 7, + }; + expect(JsonSchemaValidator.AllGangs(defaultAllGangs)).toStrictEqual(false); + }); + test("Have invalid power", () => { + const defaultAllGangs = getDefaultAllGangs() as Record; + defaultAllGangs["Slum Snakes"] = { + power: "1", + territory: 1 / 7, + }; + expect(JsonSchemaValidator.AllGangs(defaultAllGangs)).toStrictEqual(false); + }); + test("Have invalid territory", () => { + const defaultAllGangs = getDefaultAllGangs() as Record; + defaultAllGangs["Slum Snakes"] = { + power: 1, + territory: "1 / 7", + }; + expect(JsonSchemaValidator.AllGangs(defaultAllGangs)).toStrictEqual(false); + }); +}); diff --git a/test/jest/JsonSchema/StockMarketSchema.test.ts b/test/jest/JsonSchema/StockMarketSchema.test.ts new file mode 100644 index 0000000000..304ec6ab55 --- /dev/null +++ b/test/jest/JsonSchema/StockMarketSchema.test.ts @@ -0,0 +1,181 @@ +import { JsonSchemaValidator } from "../../../src/JsonSchema/JsonSchemaValidator"; +import { + deleteStockMarket, + getDefaultEmptyStockMarket, + initStockMarket, + StockMarket, +} from "../../../src/StockMarket/StockMarket"; + +deleteStockMarket(); +initStockMarket(); +// Get the clone of StockMarket immediately after calling deleteStockMarket() and initStockMarket(). +const defaultStockMarket = structuredClone(StockMarket); + +/** + * We must call this function to get the data for testing. Do not use the module-scoped "StockMarket" from + * src/StockMarket/StockMarket. Jest tests run in parallel, so our tests may mutate that variable and result in wrong + * tests. + */ +function getCloneOfDefaultStockMarket(): Record { + return structuredClone(defaultStockMarket); +} + +function getCloneOfSampleStock() { + return { + name: "ECorp", + symbol: "ECP", + price: 24618.907733048673, + lastPrice: 24702.20549043557, + playerShares: 2, + playerAvgPx: 19893.251484918932, + playerShortShares: 0, + playerAvgShortPx: 0, + mv: 0.44, + b: true, + otlkMag: 17.783525574804138, + otlkMagForecast: 68.11871382128285, + cap: 148683699, + spreadPerc: 0.5, + shareTxForMovement: 76967, + shareTxUntilMovement: 76967, + totalShares: 140600000, + maxShares: 28100000, + }; +} + +function getCloneOfSampleOrders() { + return { + ECP: [ + { + stockSymbol: "ECP", + shares: 1, + price: 1000, + type: "Limit Buy Order", + pos: "L", + }, + ], + }; +} + +type SampleStock = ReturnType; +type SampleOrders = ReturnType; + +describe("Success", () => { + test("Default empty StockMarket", () => { + expect(JsonSchemaValidator.StockMarket(getDefaultEmptyStockMarket())).toStrictEqual(true); + }); + test("Default StockMarket", () => { + expect(JsonSchemaValidator.StockMarket(getCloneOfDefaultStockMarket())).toStrictEqual(true); + }); + test("StockMarket with Orders", () => { + const stockMarket = getCloneOfDefaultStockMarket(); + stockMarket.Orders = { + ECP: [ + { + stockSymbol: "ECP", + shares: 1, + price: 1000, + type: "Limit Buy Order", + pos: "L", + }, + ], + }; + expect(JsonSchemaValidator.StockMarket(stockMarket)).toStrictEqual(true); + }); +}); + +describe("Failure", () => { + test("Have unexpected property", () => { + const stockMarket = getCloneOfDefaultStockMarket(); + stockMarket.test = ""; + expect(JsonSchemaValidator.StockMarket(stockMarket)).toStrictEqual(false); + }); + + test("Do not have lastUpdate property", () => { + const stockMarket = getCloneOfDefaultStockMarket(); + delete stockMarket.lastUpdate; + expect(JsonSchemaValidator.StockMarket(stockMarket)).toStrictEqual(false); + }); + + describe("Invalid stock", () => { + test("Have invalid type of stock", () => { + const stockMarket = getCloneOfDefaultStockMarket(); + stockMarket.ECorp = []; + expect(JsonSchemaValidator.StockMarket(stockMarket)).toStrictEqual(false); + }); + test("Have invalid stock name 1", () => { + const stockMarket = getCloneOfDefaultStockMarket(); + stockMarket.ECorp1 = getCloneOfSampleStock(); + expect(JsonSchemaValidator.StockMarket(stockMarket)).toStrictEqual(false); + }); + test("Have invalid stock name 2", () => { + const stockMarket = getCloneOfDefaultStockMarket(); + stockMarket.ECorp = getCloneOfSampleStock(); + (stockMarket.ECorp as SampleStock).name = "ECorp1"; + expect(JsonSchemaValidator.StockMarket(stockMarket)).toStrictEqual(false); + }); + test("Have invalid stock symbol", () => { + const stockMarket = getCloneOfDefaultStockMarket(); + stockMarket.ECorp = getCloneOfSampleStock(); + (stockMarket.ECorp as SampleStock).symbol = "ECP1"; + expect(JsonSchemaValidator.StockMarket(stockMarket)).toStrictEqual(false); + }); + const sampleStock = getCloneOfSampleStock(); + for (const [key, value] of Object.entries(sampleStock)) { + if (typeof value === "string") { + continue; + } + test(`Have invalid ${key}`, () => { + const stockMarket = getCloneOfDefaultStockMarket(); + stockMarket.ECorp = getCloneOfSampleStock(); + (stockMarket.ECorp as { [_: string]: unknown })[key] = "test"; + expect(JsonSchemaValidator.StockMarket(stockMarket)).toStrictEqual(false); + }); + } + }); + + describe("Invalid Orders", () => { + test("Have invalid type of Orders", () => { + const stockMarket = getCloneOfDefaultStockMarket(); + stockMarket.Orders = []; + expect(JsonSchemaValidator.StockMarket(stockMarket)).toStrictEqual(false); + }); + test("Have invalid Order: Invalid symbol 1", () => { + const stockMarket = getCloneOfDefaultStockMarket(); + stockMarket.Orders = { + ECP1: [], + }; + expect(JsonSchemaValidator.StockMarket(stockMarket)).toStrictEqual(false); + }); + test("Have invalid Order: Invalid symbol 2", () => { + const stockMarket = getCloneOfDefaultStockMarket(); + stockMarket.Orders = getCloneOfSampleOrders(); + (stockMarket.Orders as SampleOrders).ECP[0].stockSymbol = "ECP1"; + expect(JsonSchemaValidator.StockMarket(stockMarket)).toStrictEqual(false); + }); + test("Have invalid Order: Invalid order type", () => { + const stockMarket = getCloneOfDefaultStockMarket(); + stockMarket.Orders = getCloneOfSampleOrders(); + (stockMarket.Orders as SampleOrders).ECP[0].type = "Limit Buy Order1"; + expect(JsonSchemaValidator.StockMarket(stockMarket)).toStrictEqual(false); + }); + test("Have invalid Order: Invalid position type", () => { + const stockMarket = getCloneOfDefaultStockMarket(); + stockMarket.Orders = getCloneOfSampleOrders(); + (stockMarket.Orders as SampleOrders).ECP[0].pos = "L1"; + expect(JsonSchemaValidator.StockMarket(stockMarket)).toStrictEqual(false); + }); + test("Have invalid Order: Invalid shares", () => { + const stockMarket = getCloneOfDefaultStockMarket(); + stockMarket.Orders = getCloneOfSampleOrders(); + ((stockMarket.Orders as SampleOrders).ECP[0].shares as unknown) = "1"; + expect(JsonSchemaValidator.StockMarket(stockMarket)).toStrictEqual(false); + }); + test("Have invalid Order: Invalid price", () => { + const stockMarket = getCloneOfDefaultStockMarket(); + stockMarket.Orders = getCloneOfSampleOrders(); + ((stockMarket.Orders as SampleOrders).ECP[0].price as unknown) = "1000"; + expect(JsonSchemaValidator.StockMarket(stockMarket)).toStrictEqual(false); + }); + }); +}); From 4cde97bb7af604e14caf7320b90764c5c5b1b5ae Mon Sep 17 00:00:00 2001 From: CatLover <152669316+catloversg@users.noreply.github.com> Date: Wed, 20 Nov 2024 20:27:02 +0700 Subject: [PATCH 2/5] Update based on feedback --- src/JsonSchema/Data/AllGangsSchema.ts | 30 ++++++---------- src/JsonSchema/Data/StockMarketSchema.ts | 36 +++++++++---------- src/StockMarket/StockMarket.tsx | 2 -- .../jest/JsonSchema/StockMarketSchema.test.ts | 31 ---------------- 4 files changed, 27 insertions(+), 72 deletions(-) diff --git a/src/JsonSchema/Data/AllGangsSchema.ts b/src/JsonSchema/Data/AllGangsSchema.ts index bc73caa23c..7d5184dbd8 100644 --- a/src/JsonSchema/Data/AllGangsSchema.ts +++ b/src/JsonSchema/Data/AllGangsSchema.ts @@ -1,7 +1,15 @@ import { JSONSchemaType } from "ajv"; import type { AllGangs } from "../../Gang/AllGangs"; -import { FactionName } from "@enums"; +import { GangConstants } from "../../Gang/data/Constants"; +/** + * If we add/remove gangs, we must change 3 places: + * - src\Gang\AllGangs.ts: getDefaultAllGangs + * - src\Gang\data\Constants.ts: GangConstants.Names + * - src\Gang\data\power.ts: PowerMultiplier + * + * Gang code assumes that save data contains exactly gangs defined in these places. + */ export const AllGangsSchema: JSONSchemaType = { $schema: "http://json-schema.org/draft-07/schema#", type: "object", @@ -20,23 +28,7 @@ export const AllGangsSchema: JSONSchemaType = { }, }, propertyNames: { - enum: [ - FactionName.SlumSnakes, - FactionName.Tetrads, - FactionName.TheSyndicate, - FactionName.TheDarkArmy, - FactionName.SpeakersForTheDead, - FactionName.NiteSec, - FactionName.TheBlackHand, - ], + enum: GangConstants.Names, }, - required: [ - FactionName.SlumSnakes, - FactionName.Tetrads, - FactionName.TheSyndicate, - FactionName.TheDarkArmy, - FactionName.SpeakersForTheDead, - FactionName.NiteSec, - FactionName.TheBlackHand, - ], + required: GangConstants.Names, }; diff --git a/src/JsonSchema/Data/StockMarketSchema.ts b/src/JsonSchema/Data/StockMarketSchema.ts index 0e08c5adb0..c774024e0a 100644 --- a/src/JsonSchema/Data/StockMarketSchema.ts +++ b/src/JsonSchema/Data/StockMarketSchema.ts @@ -1,12 +1,10 @@ -import { OrderType, PositionType, StockSymbol } from "@enums"; +import { OrderType, PositionType } from "@enums"; import { getKeyList } from "../../utils/helpers/getKeyList"; import { Stock } from "../../StockMarket/Stock"; import { Order } from "../../StockMarket/Order"; -const stockSymbolKeys = Object.keys(StockSymbol); -const stockSymbolValues = Object.values(StockSymbol); -const stockKeys = getKeyList(Stock); -const orderKeys = getKeyList(Order); +const stockObjectProperties = getKeyList(Stock); +const orderObjectProperties = getKeyList(Order); /** * It's intentional to not use JSONSchemaType here. The data structure of StockMarket is not suitable for the usage of @@ -25,9 +23,18 @@ export const StockMarketSchema = { $schema: "http://json-schema.org/draft-07/schema#", type: "object", patternProperties: { - "^(?!.*(lastUpdate|Orders|storedCycles|ticksUntilCycle))": { + /** + * IStockMarket is an intersection type combining: + * - Record [1] + * - {lastUpdate: number;Orders: IOrderBook;storedCycles: number;ticksUntilCycle: number;} [2] + * + * StockMarketSchema contains: + * - patternProperties: Defines [1]. The following regex matches all properties that are not in [2]. It defines the + * map of "Full stock name -> Stock". + * - properties: Define [2]. + */ + "^(?!(lastUpdate|Orders|storedCycles|ticksUntilCycle))": { type: "object", - additionalProperties: false, properties: { b: { type: "boolean", @@ -46,7 +53,6 @@ export const StockMarketSchema = { }, name: { type: "string", - enum: [...stockSymbolKeys], }, otlkMag: { type: "number", @@ -80,13 +86,12 @@ export const StockMarketSchema = { }, symbol: { type: "string", - enum: [...stockSymbolValues], }, totalShares: { type: "number", }, }, - required: [...stockKeys], + required: [...stockObjectProperties], }, }, properties: { @@ -98,7 +103,6 @@ export const StockMarketSchema = { type: "array", items: { type: "object", - additionalProperties: false, properties: { pos: { type: "string", @@ -112,27 +116,19 @@ export const StockMarketSchema = { }, stockSymbol: { type: "string", - enum: [...stockSymbolValues], }, type: { type: "string", enum: [OrderType.LimitBuy, OrderType.LimitSell, OrderType.StopBuy, OrderType.StopSell], }, }, - required: [...orderKeys], + required: [...orderObjectProperties], }, }, }, - propertyNames: { - enum: [...stockSymbolValues], - }, - required: [], }, storedCycles: { type: "number" }, ticksUntilCycle: { type: "number" }, }, - propertyNames: { - enum: [...stockSymbolKeys, "lastUpdate", "Orders", "storedCycles", "ticksUntilCycle"], - }, required: ["lastUpdate", "Orders", "storedCycles", "ticksUntilCycle"], }; diff --git a/src/StockMarket/StockMarket.tsx b/src/StockMarket/StockMarket.tsx index d305ca55d6..544c6f77a4 100644 --- a/src/StockMarket/StockMarket.tsx +++ b/src/StockMarket/StockMarket.tsx @@ -168,9 +168,7 @@ export function deleteStockMarket(): void { } export function initStockMarket(): void { - console.log(Object.getOwnPropertyNames(StockMarket)); for (const stockName of Object.getOwnPropertyNames(StockMarket)) { - console.log(stockName); // eslint-disable-next-line @typescript-eslint/no-dynamic-delete delete StockMarket[stockName]; } diff --git a/test/jest/JsonSchema/StockMarketSchema.test.ts b/test/jest/JsonSchema/StockMarketSchema.test.ts index 304ec6ab55..640e44561e 100644 --- a/test/jest/JsonSchema/StockMarketSchema.test.ts +++ b/test/jest/JsonSchema/StockMarketSchema.test.ts @@ -57,7 +57,6 @@ function getCloneOfSampleOrders() { }; } -type SampleStock = ReturnType; type SampleOrders = ReturnType; describe("Success", () => { @@ -103,23 +102,6 @@ describe("Failure", () => { stockMarket.ECorp = []; expect(JsonSchemaValidator.StockMarket(stockMarket)).toStrictEqual(false); }); - test("Have invalid stock name 1", () => { - const stockMarket = getCloneOfDefaultStockMarket(); - stockMarket.ECorp1 = getCloneOfSampleStock(); - expect(JsonSchemaValidator.StockMarket(stockMarket)).toStrictEqual(false); - }); - test("Have invalid stock name 2", () => { - const stockMarket = getCloneOfDefaultStockMarket(); - stockMarket.ECorp = getCloneOfSampleStock(); - (stockMarket.ECorp as SampleStock).name = "ECorp1"; - expect(JsonSchemaValidator.StockMarket(stockMarket)).toStrictEqual(false); - }); - test("Have invalid stock symbol", () => { - const stockMarket = getCloneOfDefaultStockMarket(); - stockMarket.ECorp = getCloneOfSampleStock(); - (stockMarket.ECorp as SampleStock).symbol = "ECP1"; - expect(JsonSchemaValidator.StockMarket(stockMarket)).toStrictEqual(false); - }); const sampleStock = getCloneOfSampleStock(); for (const [key, value] of Object.entries(sampleStock)) { if (typeof value === "string") { @@ -140,19 +122,6 @@ describe("Failure", () => { stockMarket.Orders = []; expect(JsonSchemaValidator.StockMarket(stockMarket)).toStrictEqual(false); }); - test("Have invalid Order: Invalid symbol 1", () => { - const stockMarket = getCloneOfDefaultStockMarket(); - stockMarket.Orders = { - ECP1: [], - }; - expect(JsonSchemaValidator.StockMarket(stockMarket)).toStrictEqual(false); - }); - test("Have invalid Order: Invalid symbol 2", () => { - const stockMarket = getCloneOfDefaultStockMarket(); - stockMarket.Orders = getCloneOfSampleOrders(); - (stockMarket.Orders as SampleOrders).ECP[0].stockSymbol = "ECP1"; - expect(JsonSchemaValidator.StockMarket(stockMarket)).toStrictEqual(false); - }); test("Have invalid Order: Invalid order type", () => { const stockMarket = getCloneOfDefaultStockMarket(); stockMarket.Orders = getCloneOfSampleOrders(); From 8a643d81506b247f9a98b024498fcedcee79e4e3 Mon Sep 17 00:00:00 2001 From: CatLover <152669316+catloversg@users.noreply.github.com> Date: Wed, 20 Nov 2024 20:46:46 +0700 Subject: [PATCH 3/5] Update Jest test for StockMarketSchema --- test/jest/JsonSchema/StockMarketSchema.test.ts | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/test/jest/JsonSchema/StockMarketSchema.test.ts b/test/jest/JsonSchema/StockMarketSchema.test.ts index 640e44561e..eed35c7e8c 100644 --- a/test/jest/JsonSchema/StockMarketSchema.test.ts +++ b/test/jest/JsonSchema/StockMarketSchema.test.ts @@ -68,17 +68,7 @@ describe("Success", () => { }); test("StockMarket with Orders", () => { const stockMarket = getCloneOfDefaultStockMarket(); - stockMarket.Orders = { - ECP: [ - { - stockSymbol: "ECP", - shares: 1, - price: 1000, - type: "Limit Buy Order", - pos: "L", - }, - ], - }; + stockMarket.Orders = getCloneOfSampleOrders(); expect(JsonSchemaValidator.StockMarket(stockMarket)).toStrictEqual(true); }); }); From c1fecbfd6c545efcb7e7737d2a669f8a588f16ff Mon Sep 17 00:00:00 2001 From: CatLover <152669316+catloversg@users.noreply.github.com> Date: Wed, 20 Nov 2024 21:37:25 +0700 Subject: [PATCH 4/5] Update import --- src/JsonSchema/Data/AllGangsSchema.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/JsonSchema/Data/AllGangsSchema.ts b/src/JsonSchema/Data/AllGangsSchema.ts index 7d5184dbd8..baddbf5baf 100644 --- a/src/JsonSchema/Data/AllGangsSchema.ts +++ b/src/JsonSchema/Data/AllGangsSchema.ts @@ -1,4 +1,4 @@ -import { JSONSchemaType } from "ajv"; +import type { JSONSchemaType } from "ajv"; import type { AllGangs } from "../../Gang/AllGangs"; import { GangConstants } from "../../Gang/data/Constants"; From 2eba495daba1c73bb4218a82027ac013bfd161a0 Mon Sep 17 00:00:00 2001 From: CatLover <152669316+catloversg@users.noreply.github.com> Date: Thu, 21 Nov 2024 17:59:11 +0700 Subject: [PATCH 5/5] Add comments --- src/JsonSchema/Data/AllGangsSchema.ts | 3 ++- src/JsonSchema/Data/StockMarketSchema.ts | 7 +++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/JsonSchema/Data/AllGangsSchema.ts b/src/JsonSchema/Data/AllGangsSchema.ts index baddbf5baf..5f6baad04a 100644 --- a/src/JsonSchema/Data/AllGangsSchema.ts +++ b/src/JsonSchema/Data/AllGangsSchema.ts @@ -3,10 +3,11 @@ import type { AllGangs } from "../../Gang/AllGangs"; import { GangConstants } from "../../Gang/data/Constants"; /** - * If we add/remove gangs, we must change 3 places: + * If we add/remove gangs, we must change 4 things: * - src\Gang\AllGangs.ts: getDefaultAllGangs * - src\Gang\data\Constants.ts: GangConstants.Names * - src\Gang\data\power.ts: PowerMultiplier + * - Save file migration code. * * Gang code assumes that save data contains exactly gangs defined in these places. */ diff --git a/src/JsonSchema/Data/StockMarketSchema.ts b/src/JsonSchema/Data/StockMarketSchema.ts index c774024e0a..8371660157 100644 --- a/src/JsonSchema/Data/StockMarketSchema.ts +++ b/src/JsonSchema/Data/StockMarketSchema.ts @@ -32,6 +32,13 @@ export const StockMarketSchema = { * - patternProperties: Defines [1]. The following regex matches all properties that are not in [2]. It defines the * map of "Full stock name -> Stock". * - properties: Define [2]. + * + * Note that with [1], our code allows unknown stocks. Let's say the player loads a save file with this entry in + * [1]: UnknownCorp123 -> Stock with symbol UCP123. Although this stock is not in our list of "valid" stocks, we + * still process it normally. By "tolerating" unknown stocks, we allow loading a save file created in: + * - Old versions with unsupported stocks: In very old versions (v1.2.0 and older ones), the "full stock name" of + * "Joe's Guns" is "Joes Guns". + * - New versions with unknown stocks. */ "^(?!(lastUpdate|Orders|storedCycles|ticksUntilCycle))": { type: "object",