fix(admin): I4 — devPlainCode-баннер за import.meta.env.DEV
This commit is contained in:
@@ -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"
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user