mirror of
https://github.com/tiennm99/vngeoguessr.git
synced 2026-10-14 22:17:48 +00:00
A dry pool and an upstream failure were told apart by matching error message prefixes in eight places; they are now DryPoolError and UpstreamError, and /api/new-game answers 404 with the coverage message for the first and 502 for the second instead of 200 for both. The draw stops retrying after eight seconds and the client gives up at fifteen. /api/guess returns gameResult alone: the whole leaderboard and distance objects, four legacy rank fields and a message duplicated it and had no reader. The leaderboard module drops its pre-tree aliases and the submitScore primitive with no production caller; tests award points through submitRoundScore, the path production runs. /api/leaderboard returns the rows alone. The unused debug POST on new-game, zScore, isDay and indexedProvinces go with them. Fixes two live bugs: GameClient dropped the partial flag, so a half-credited round showed the success message; and the map centre was state seeded to Ho Chi Minh, painting one frame there on every region. The e2e stubs carry every key the routes emit and a test now asserts it.
114 lines
3.8 KiB
JavaScript
114 lines
3.8 KiB
JavaScript
import { mkdtempSync, writeFileSync, rmSync } from 'node:fs';
|
|
import { tmpdir } from 'node:os';
|
|
import { join } from 'node:path';
|
|
import { describe, it, expect, afterEach } from 'vitest';
|
|
import { parseEnv, loadEnvFile, applyEnvFile } from '../scripts/lib/env.mjs';
|
|
|
|
// The four offline pipeline scripts (build-pano-index, build-region-boundaries
|
|
// via seed-pano-db and friends) used to each carry their own copy of this
|
|
// parser. One of the copies had drifted -- it never stripped the quotes
|
|
// `vercel env pull` wraps values in -- so this pins the shared parser's rules
|
|
// directly, from text, rather than exercising it through any one script.
|
|
|
|
describe('parseEnv', () => {
|
|
it('parses simple key=value pairs', () => {
|
|
expect(parseEnv('FOO=bar\nBAZ=qux')).toEqual({ FOO: 'bar', BAZ: 'qux' });
|
|
});
|
|
|
|
it('strips a `vercel env pull`-style quoted value', () => {
|
|
expect(parseEnv('DATABASE_URL="postgres://example.test/db"')).toEqual({
|
|
DATABASE_URL: 'postgres://example.test/db',
|
|
});
|
|
});
|
|
|
|
it('keeps an unquoted value with embedded quotes as-is', () => {
|
|
expect(parseEnv('TOKEN=abc"def')).toEqual({ TOKEN: 'abc"def' });
|
|
});
|
|
|
|
it('skips comments and blank lines', () => {
|
|
expect(parseEnv('# a comment\n\nFOO=bar\n \n# another\nBAZ=qux')).toEqual({
|
|
FOO: 'bar',
|
|
BAZ: 'qux',
|
|
});
|
|
});
|
|
|
|
it('splits only on the first `=`, keeping the rest in the value', () => {
|
|
expect(parseEnv('CONN_STR=postgres://user:pass@host/db?a=1&b=2')).toEqual({
|
|
CONN_STR: 'postgres://user:pass@host/db?a=1&b=2',
|
|
});
|
|
});
|
|
|
|
it('trims surrounding whitespace around key and value', () => {
|
|
expect(parseEnv(' FOO = bar ')).toEqual({ FOO: 'bar' });
|
|
});
|
|
|
|
it('ignores a line with no `=`', () => {
|
|
expect(parseEnv('FOO=bar\nnot-a-line\nBAZ=qux')).toEqual({ FOO: 'bar', BAZ: 'qux' });
|
|
});
|
|
|
|
it('handles CRLF line endings', () => {
|
|
expect(parseEnv('FOO=bar\r\nBAZ=qux\r\n')).toEqual({ FOO: 'bar', BAZ: 'qux' });
|
|
});
|
|
|
|
it('returns {} for empty text', () => {
|
|
expect(parseEnv('')).toEqual({});
|
|
});
|
|
});
|
|
|
|
describe('loadEnvFile', () => {
|
|
let dir;
|
|
|
|
afterEach(() => {
|
|
if (dir) rmSync(dir, { recursive: true, force: true });
|
|
dir = undefined;
|
|
});
|
|
|
|
it('reads and parses an existing file', () => {
|
|
dir = mkdtempSync(join(tmpdir(), 'vngeoguessr-env-test-'));
|
|
const path = join(dir, '.env');
|
|
writeFileSync(path, 'MAPILLARY_ACCESS_TOKEN="secret-token"\n# comment\nEMPTY_OK=\n');
|
|
expect(loadEnvFile(path)).toEqual({ MAPILLARY_ACCESS_TOKEN: 'secret-token', EMPTY_OK: '' });
|
|
});
|
|
|
|
it('returns {} when the file does not exist', () => {
|
|
dir = mkdtempSync(join(tmpdir(), 'vngeoguessr-env-test-'));
|
|
expect(loadEnvFile(join(dir, 'missing.env'))).toEqual({});
|
|
});
|
|
});
|
|
|
|
describe('applyEnvFile', () => {
|
|
const KEY = 'VNGEOGUESSR_TEST_ENV_VAR';
|
|
let dir;
|
|
|
|
afterEach(() => {
|
|
delete process.env[KEY];
|
|
if (dir) rmSync(dir, { recursive: true, force: true });
|
|
dir = undefined;
|
|
});
|
|
|
|
it('sets a variable from the file when unset in the environment', () => {
|
|
dir = mkdtempSync(join(tmpdir(), 'vngeoguessr-env-test-'));
|
|
const path = join(dir, '.env');
|
|
writeFileSync(path, `${KEY}=from-file\n`);
|
|
delete process.env[KEY];
|
|
applyEnvFile(path);
|
|
expect(process.env[KEY]).toBe('from-file');
|
|
});
|
|
|
|
it('does not override a variable the environment already set', () => {
|
|
dir = mkdtempSync(join(tmpdir(), 'vngeoguessr-env-test-'));
|
|
const path = join(dir, '.env');
|
|
writeFileSync(path, `${KEY}=from-file\n`);
|
|
process.env[KEY] = 'from-environment';
|
|
applyEnvFile(path);
|
|
expect(process.env[KEY]).toBe('from-environment');
|
|
});
|
|
|
|
it('is a no-op when the file is missing', () => {
|
|
dir = mkdtempSync(join(tmpdir(), 'vngeoguessr-env-test-'));
|
|
delete process.env[KEY];
|
|
expect(() => applyEnvFile(join(dir, 'missing.env'))).not.toThrow();
|
|
expect(process.env[KEY]).toBeUndefined();
|
|
});
|
|
});
|