Приложение обкатывается на боевом контуре, поэтому интерфейс надо проверять на живых данных — а живые данные лежат у настоящих водителей. * «Вход для сотрудника» (/dev) — логин учётки CRM, поддержан второй шаг 2FA; дальше поиск водителя по ФИО/телефону/id и просмотр его экранов. * Плашка «Смотрю как …» над всеми экранами: интерфейс в этом режиме неотличим от водительского, и чужой баланс легко принять за свой. * Вкладка «Отладка» (/debug, водителю недоступна): смена водителя, driver_id, режим, срок токена, адрес API, коммит и время сборки, журнал последних 40 ответивших запросов, сброс кэша сервис-воркера. * Пополнение под просмотром идёт в учебный контур: экран /mock-pay/:orderId выбирает, что «ответит» банк, а настоящий экран оплаты разбирает эти статусы своим боевым кодом. Ни рубля, ни чека 54-ФЗ. Заодно доезжает незакоммиченная работа прошлой сессии: дев-прокси в vite.config.ts, его тест и разобранный .env.example. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
104 lines
5.5 KiB
TypeScript
104 lines
5.5 KiB
TypeScript
// @vitest-environment node
|
|
/**
|
|
* Локальная разработка PWA водителя.
|
|
*
|
|
* Гейт CORS на бэкенде (CORS_ALLOW_LOCALHOST, по умолчанию выключен) закрыл
|
|
* дыру, но заодно оставил этот фронт без рабочего дева: прокси не было, а
|
|
* client.ts по умолчанию уходит на боевой https://crm.pptaxi.ru/api — то есть
|
|
* «включи CORS_ALLOW_LOCALHOST» тут не помогало, запрос вообще не доходил до
|
|
* локального бэкенда.
|
|
*
|
|
* Тесты держат обе половины договорённости:
|
|
* 1) `npm run dev` доводит фронт до локального бэкенда сам, без правки .env;
|
|
* 2) боевая сборка от этого не меняется — относительная дев-база в бандл
|
|
* попасть не может (в проде он живёт на app.pptaxi.ru, где /api нет).
|
|
*
|
|
* Конфиг резолвится по-настоящему (resolveConfig читает vite.config.ts и
|
|
* env-файлы) — иначе тест проверял бы не то, что увидит vite.
|
|
*
|
|
* Файл лежит рядом с vite.config.ts и НЕ называется vite.config.test.ts:
|
|
* в дефолтном exclude vitest есть `**\/{...,vite,...}.config.*`, под который
|
|
* такое имя попадает целиком — тест молча не запускался бы.
|
|
*/
|
|
import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
|
import path from "path";
|
|
import { fileURLToPath } from "url";
|
|
import { resolveConfig } from "vite";
|
|
|
|
const ROOT = path.dirname(fileURLToPath(import.meta.url));
|
|
|
|
/** Обе переменные — process.env, и dev-дефолт их же и трогает: сохраняем. */
|
|
const SAVED = {
|
|
base: process.env.VITE_API_BASE,
|
|
target: process.env.VITE_API_TARGET,
|
|
};
|
|
|
|
function restore(key: "VITE_API_BASE" | "VITE_API_TARGET", value: string | undefined): void {
|
|
if (value === undefined) delete process.env[key];
|
|
else process.env[key] = value;
|
|
}
|
|
|
|
beforeEach(() => {
|
|
delete process.env.VITE_API_BASE;
|
|
delete process.env.VITE_API_TARGET;
|
|
});
|
|
|
|
afterEach(() => {
|
|
restore("VITE_API_BASE", SAVED.base);
|
|
restore("VITE_API_TARGET", SAVED.target);
|
|
});
|
|
|
|
const resolve = (command: "serve" | "build", mode: "development" | "production") =>
|
|
resolveConfig({ root: ROOT, configFile: path.join(ROOT, "vite.config.ts"), logLevel: "silent" },
|
|
command, mode, mode);
|
|
|
|
describe("dev-сервер доводит фронт до локального бэкенда", () => {
|
|
it("подставляет относительную базу — запрос идёт со своего origin, CORS не участвует", async () => {
|
|
const cfg = await resolve("serve", "development");
|
|
expect(cfg.env.VITE_API_BASE).toBe("/api");
|
|
});
|
|
|
|
it("проксирует /api на локальный бэкенд, а не на боевой домен", async () => {
|
|
const cfg = await resolve("serve", "development");
|
|
const proxy = cfg.server.proxy?.["/api"];
|
|
expect(typeof proxy === "object" && proxy.target).toBe("http://127.0.0.1:8000");
|
|
});
|
|
|
|
it("цель прокси переопределяется VITE_API_TARGET (стенд, тайлнет)", async () => {
|
|
process.env.VITE_API_TARGET = "http://127.0.0.1:8090";
|
|
const cfg = await resolve("serve", "development");
|
|
const proxy = cfg.server.proxy?.["/api"];
|
|
expect(typeof proxy === "object" && proxy.target).toBe("http://127.0.0.1:8090");
|
|
});
|
|
|
|
it("база и путь прокси — одно и то же", async () => {
|
|
// Две половины рецепта живут в разных местах конфига: базу подставляем в
|
|
// env, путь перечисляем в server.proxy. Разъедутся (например, база станет
|
|
// "/backend") — dev-сервер отдаст на этот путь index.html вместо ответа
|
|
// бэкенда, и падать будет уже в разборе JSON, далеко от причины.
|
|
const cfg = await resolve("serve", "development");
|
|
expect(Object.keys(cfg.server.proxy ?? {})).toContain(cfg.env.VITE_API_BASE);
|
|
});
|
|
|
|
it("не перебивает базу, заданную разработчиком", async () => {
|
|
process.env.VITE_API_BASE = "https://stand.example/api";
|
|
const cfg = await resolve("serve", "development");
|
|
expect(cfg.env.VITE_API_BASE).toBe("https://stand.example/api");
|
|
});
|
|
});
|
|
|
|
describe("боевая сборка остаётся боевой", () => {
|
|
it("дев-дефолт в сборку не течёт: base пустая → client.ts берёт прод-адрес", async () => {
|
|
const cfg = await resolve("build", "production");
|
|
expect(cfg.env.VITE_API_BASE).toBeUndefined();
|
|
expect(process.env.VITE_API_BASE).toBeUndefined();
|
|
});
|
|
|
|
it("ни один env-файл не подсовывает сборке относительную базу", async () => {
|
|
// Относительный /api в проде означал бы app.pptaxi.ru/api — такого нет.
|
|
// Ловит и коммит .env с дев-значением, и локальный .env на машине сборки.
|
|
const cfg = await resolve("build", "production");
|
|
expect(cfg.env.VITE_API_BASE).not.toBe("/api");
|
|
});
|
|
});
|