Improve MineGeo interface and functionality

This commit is contained in:
2026-10-06 19:24:41 +05:00
parent 7404f1dc1c
commit 233e4d57f0
67 changed files with 3053 additions and 643 deletions

190
tests/deploy-vercel.test.ts Normal file
View File

@@ -0,0 +1,190 @@
import assert from "node:assert/strict";
import { test } from "node:test";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import { execFileSync } from "node:child_process";
import { zipSync, strToU8 } from "fflate";
// The command is also an importable module, so tests never trigger a real upload.
import {
deploymentFiles,
deploymentPayload,
deploy,
} from "../scripts/deploy-vercel.mjs";
function fixture(
run: (
root: string,
write: (file: string, data: string | Uint8Array) => void,
) => void,
) {
const root = fs.mkdtempSync(path.join(os.tmpdir(), "minegeo-deploy-test-"));
function write(file: string, data: string | Uint8Array) {
fs.mkdirSync(path.dirname(path.join(root, file)), { recursive: true });
fs.writeFileSync(path.join(root, file), data);
}
try {
write(
"package.json",
JSON.stringify({
name: "minegeo",
scripts: { worker: "private worker" },
repository: "https://private.invalid/repository",
}),
);
write("app/layout.tsx", "export default function Layout() {}");
run(root, write);
} finally {
assert.ok(root.startsWith(path.join(os.tmpdir(), "minegeo-deploy-test-")));
fs.rmSync(root, { recursive: true, force: true });
}
}
test("deployment collects web assets and excludes local secrets, Git and build outputs", () => {
fixture((root, write) => {
write(".env.local", "SUPABASE_SERVICE_ROLE_KEY=secret");
write(".env.deploy.local", "VERCEL_TOKEN=secret");
write(".git/config", "private Git configuration");
write("public/.hidden", "secret");
write("public/generated/capture.png", "private capture");
write("public/downloads/old.jar", "generated artifact");
write("app/downloads/page.tsx", "export default function Downloads() {}");
write("worker/main.ts", "private worker");
write("public/images/map.svg", "<svg/>");
write("components/Example.tsx", "export const Example = 1;");
const files = deploymentFiles(root);
assert.deepEqual(
files.map((entry: { file: string }) => entry.file).sort(),
[
"app/downloads/page.tsx",
"app/layout.tsx",
"components/Example.tsx",
"package.json",
"public/images/map.svg",
],
);
const pkg = JSON.parse(
Buffer.from(
files.find((entry: { file: string }) => entry.file === "package.json")
.data,
"base64",
).toString(),
);
assert.equal(pkg.repository, undefined);
assert.equal(pkg.scripts.worker, undefined);
const payload = deploymentPayload(files);
assert.equal(payload.target, "production");
assert.equal(deploymentPayload(files, true).target, undefined);
assert.equal(Object.hasOwn(payload, "gitSource"), false);
assert.equal(Object.hasOwn(payload, "gitMetadata"), false);
assert.equal(
JSON.stringify(payload).includes("SUPABASE_SERVICE_ROLE_KEY"),
false,
);
});
});
test("privacy scan rejects the private Git hostname in public files and bundled JARs", () => {
fixture((root, write) => {
execFileSync("git", ["init", root], { windowsHide: true, stdio: "ignore" });
execFileSync(
"git",
[
"-C",
root,
"remote",
"add",
"origin",
"https://private.example.invalid/user/minegeo.git",
],
{ windowsHide: true },
);
write("public/leak.txt", "https://private.example.invalid");
assert.throws(
() => deploymentFiles(root),
/Private Git server reference found in public\/leak.txt/,
);
fs.unlinkSync(path.join(root, "public/leak.txt"));
write(
"public/tools/mod.jar",
zipSync({ "metadata.json": strToU8('"private.example.invalid"') }),
);
assert.throws(
() => deploymentFiles(root),
/Private Git server reference found in public\/tools\/mod.jar/,
);
});
});
test("deploy sends every file in one POST, then polls until READY", async () => {
const requests: { url: string; init: RequestInit }[] = [];
const states = ["QUEUED", "BUILDING", "READY"];
const logs: string[] = [];
const payload = deploymentPayload([
{ file: "app/page.tsx", data: "YWJj", encoding: "base64" },
]);
const result = await deploy(payload, {
token: "test-token",
wait: async () => {},
log: (message: string) => logs.push(message),
request: async (url, init = {}) => {
requests.push({ url: String(url), init });
return new Response(
JSON.stringify({
id: "test-deployment",
readyState: states.shift(),
url: "preview.example.invalid",
}),
{ status: 200 },
);
},
});
assert.equal(result.readyState, "READY");
assert.equal(
requests.filter((item) => item.init.method === "POST").length,
1,
);
assert.deepEqual(
JSON.parse(requests[0].init.body as string).files,
payload.files,
);
assert.equal(
requests.slice(1).every((item) => item.init.method === "GET"),
true,
);
assert.equal(
logs.some((message) => message.includes("test-token")),
false,
);
});
test("failed builds and rejected authentication never report success", async () => {
const payload = deploymentPayload([]);
await assert.rejects(
deploy(payload, {
token: "test",
log: () => {},
request: async () =>
new Response(
JSON.stringify({
id: "failed",
readyState: "ERROR",
errorMessage: "Build failed",
}),
{ status: 200 },
),
}),
/Build failed/,
);
await assert.rejects(
deploy(payload, {
token: "test",
log: () => {},
request: async () =>
new Response(JSON.stringify({ error: { message: "Unauthorized" } }), {
status: 401,
}),
}),
/Vercel 401/,
);
});

82
tests/i18n.test.ts Normal file
View File

@@ -0,0 +1,82 @@
import assert from "node:assert/strict";
import { test } from "node:test";
import fs from "node:fs";
import path from "node:path";
import ts from "typescript";
import russian from "../shared/locales/ru.json";
import { detectLocale, resolveLocale, translator } from "../shared/i18n";
test("browser language preferences support regional tags and weighted fallbacks", () => {
assert.equal(detectLocale("ru-RU,ru;q=0.9,en;q=0.8"), "ru");
assert.equal(detectLocale("en-US;q=0.3,ru;q=0.9"), "ru");
assert.equal(detectLocale("de-DE,ru;q=0.7,en;q=0.5"), "ru");
assert.equal(detectLocale("ru;q=0,en-GB;q=0.8"), "en");
assert.equal(detectLocale("ru;q=invalid,en"), "en");
assert.equal(detectLocale(null), "en");
assert.equal(detectLocale("fr-FR"), "en");
});
test("an explicit language choice overrides automatic detection", () => {
assert.equal(resolveLocale("en", "ru-RU"), "en");
assert.equal(resolveLocale("ru", "en-US"), "ru");
assert.equal(resolveLocale("auto", "ru"), "ru");
assert.equal(resolveLocale("unexpected", "ru"), "ru");
});
test("translations interpolate values without changing player content", () => {
const ru = translator("ru"),
en = translator("en");
assert.equal(ru("Log in"), "Войти");
assert.equal(en("Log in"), "Log in");
assert.equal(
ru("Keep {location}", { location: "Steve's Castle" }),
"Оставить: Steve's Castle",
);
assert.equal(ru("Map authored by Steve"), "Map authored by Steve");
assert.equal(ru("Keep {location}"), "Оставить: {location}");
});
test("all literal interface messages have Russian translations with matching placeholders", () => {
const missing: string[] = [],
mismatches: string[] = [];
const dictionary = russian as Record<string, string>;
const placeholders = (s: string) =>
[...s.matchAll(/\{(\w+)\}/g)].map((match) => match[1]).sort();
function walk(directory: string) {
for (const entry of fs.readdirSync(directory, { withFileTypes: true })) {
const file = path.join(directory, entry.name);
if (entry.isDirectory()) walk(file);
else if (file.endsWith(".tsx")) {
const source = ts.createSourceFile(
file,
fs.readFileSync(file, "utf8"),
ts.ScriptTarget.Latest,
true,
ts.ScriptKind.TSX,
);
function visit(node: ts.Node) {
if (
ts.isCallExpression(node) &&
node.expression.getText(source) === "t" &&
node.arguments[0] &&
ts.isStringLiteral(node.arguments[0])
) {
const message = node.arguments[0].text;
if (!dictionary[message]) missing.push(`${file}: ${message}`);
else if (
JSON.stringify(placeholders(message)) !==
JSON.stringify(placeholders(dictionary[message]))
)
mismatches.push(message);
}
ts.forEachChild(node, visit);
}
visit(source);
}
}
}
walk("app");
walk("components");
assert.deepEqual(missing, []);
assert.deepEqual(mismatches, []);
});

View File

@@ -0,0 +1,19 @@
import assert from "node:assert/strict";
import { test } from "node:test";
import { siteSettingsSchema } from "../shared/site-settings";
test("site settings accept only an explicit boolean and reject privilege fields", () => {
for (const enabled of [false, true])
assert.equal(
siteSettingsSchema.parse({ betaBadgeEnabled: enabled }).betaBadgeEnabled,
enabled,
);
for (const payload of [
{},
{ betaBadgeEnabled: "false" },
{ betaBadgeEnabled: null },
{ betaBadgeEnabled: false, role: "moderator" },
{ betaBadgeEnabled: false, updatedBy: "another-player" },
])
assert.equal(siteSettingsSchema.safeParse(payload).success, false);
});