fix(admin): I4 — devPlainCode-баннер за import.meta.env.DEV

This commit is contained in:
Дмитрий
2026-05-17 06:45:54 +03:00
parent 07d2e9705e
commit ff96e2f7ad
2 changed files with 33 additions and 3 deletions
@@ -5,7 +5,7 @@
* 3-step state-machine:
* 1. 'reason' — textarea для основания (≥30 chars) → POST /api/admin/impersonation/init.
* 2. 'verify' — показ email клиента + ввод 6-значного кода → /api/admin/impersonation/verify.
* На dev показывается _dev_plain_code (на prod исчезнет после MailService).
* На dev показывается _dev_plain_code (за import.meta.env.DEV; на prod баннер не рендерится).
* 3. 'active' — chip «Сессия активна», кнопка «Завершить» → /api/admin/impersonation/end.
*
* NB: на MVP saas-admin auth не реализован, requested_by передаётся параметром
@@ -49,6 +49,10 @@ const expiresAt = ref<string | null>(null);
const devPlainCode = ref<string | null>(null);
const usedAtIso = ref<string | null>(null);
// I4: явный frontend DEV-gate. import.meta.env.DEV статически заменяется Vite —
// в prod-сборке = false, баннер с плейн-кодом tree-shake'ится.
const isDevEnv = import.meta.env.DEV;
const reasonLength = computed(() => reason.value.trim().length);
const reasonRemaining = computed(() => Math.max(0, 30 - reasonLength.value));
const reasonValid = computed(() => reasonLength.value >= 30);
@@ -216,7 +220,7 @@ function close() {
data-testid="code-input"
/>
<v-alert
v-if="devPlainCode"
v-if="isDevEnv && devPlainCode"
type="success"
variant="tonal"
density="compact"
+27 -1
View File
@@ -1,4 +1,4 @@
import { describe, it, expect, beforeEach, vi } from 'vitest';
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
import { mount, flushPromises } from '@vue/test-utils';
import { createVuetify } from 'vuetify';
@@ -52,6 +52,10 @@ describe('ImpersonationDialog.vue', () => {
vi.clearAllMocks();
});
afterEach(() => {
vi.unstubAllEnvs();
});
it('не рендерит content когда modelValue=false', () => {
const wrapper = factory({ modelValue: false, tenant: sampleTenant });
expect(wrapper.find('.dialog-stub').exists()).toBe(false);
@@ -198,4 +202,26 @@ describe('ImpersonationDialog.vue', () => {
expect(events).toBeDefined();
expect(events?.[0]).toEqual([false]);
});
it('I4 — dev-code-banner НЕ рендерится когда import.meta.env.DEV=false (prod)', async () => {
vi.stubEnv('DEV', false);
vi.mocked(adminApi.impersonationInit).mockResolvedValue({
token_id: 42,
expires_at: '2026-05-09T12:00:00Z',
sent_to_email: 'admin@okna-moscow.ru',
_dev_plain_code: '123456',
});
const wrapper = factory({ modelValue: true, tenant: sampleTenant });
await wrapper.find('[data-testid="reason-input"] textarea').setValue(
'Тикет SUP-12453: клиент сообщил, что в карточке сделки не сохраняется коммент.',
);
await wrapper.find('[data-testid="submit-init-btn"]').trigger('click');
await flushPromises();
// step verify достигнут — devPlainCode='123456' есть, но DEV=false → баннер не рендерится
expect(wrapper.text()).toContain('Код отправлен на email клиента');
expect(wrapper.find('[data-testid="dev-code-banner"]').exists()).toBe(false);
});
});