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