Files
bedolaga-cabinet/src/utils/supportContact.test.ts
airp0wer ebde1f6174 fix(support): ужесточить resolveSupportContact — схема, legacy-username, null
Инкремент поверх f04675b (#501 был технически перекрыт им). resolveSupportContact
теперь не только не клеит t.me с внешним URL, но и отсекает битый конфиг вместо
того, чтобы уводить пользователя по мусорной ссылке:

1. Валидация схемы support_url — открываем только http/https/tg, чужая схема
   (javascript:, data: и т.п.) больше не уходит в опенер как есть, а даёт null.
2. Валидация legacy username регуляркой ^[A-Za-z0-9_]{3,}$ — старый бэк с
   URL-образным SUPPORT_USERNAME больше не собирает битый t.me/https://…,
   URL-образное отдаём резолвить бэку.
3. null → кнопка не рендерится — при ненастроенном/битом контакте (в т.ч. ушёл
   дефолт @support) кнопку в Support.tsx не показываем, а не открываем молча
   https://t.me/support.

Заодно url-ветка в ticketsDisabled и карточка both-режима ходят через
resolveSupportContact — ушёл прямой openLink(support_url!) с non-null assertion,
три ветки getSupportMessage схлопнуты в две (profile и fallback идентичны).
2026-07-21 10:28:31 +03:00

98 lines
3.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { describe, expect, it } from 'vitest';
import type { SupportConfig } from '../types';
import { resolveSupportContact } from './supportContact';
/**
* Кабинет склеивал `https://t.me/${support_username}` и ломал внешний хелпдеск:
* SUPPORT_USERNAME принимает и `@user`, и произвольный URL, поэтому из
* `https://help.example.com` получалось `https://t.me/https://help.example.com`.
* Бэк отдаёт контакт уже разрезолвленным — клеить на клиенте больше нечего.
*
* resolveSupportContact дополнительно защищается от битого конфига: чужие схемы
* (`javascript:` и т.п.) и URL-образный legacy-username не уводят в опенер, а
* возвращают null — кнопку в таком случае не рендерим.
*/
const config = (overrides: Partial<SupportConfig>): SupportConfig => ({
tickets_enabled: true,
support_type: 'both',
...overrides,
});
describe('resolveSupportContact', () => {
it('внешний хелпдеск открывается как обычная ссылка, без t.me', () => {
const target = resolveSupportContact(
config({
support_url: 'https://help.example.com',
support_username: 'https://help.example.com',
contact_is_telegram: false,
}),
);
expect(target).toEqual({ kind: 'external', url: 'https://help.example.com' });
expect(target?.url).not.toContain('t.me');
});
it('телеграм-контакт открывается через telegram-ссылку', () => {
const target = resolveSupportContact(
config({
support_url: 'https://t.me/help',
support_username: '@help',
contact_is_telegram: true,
}),
);
expect(target).toEqual({ kind: 'telegram', url: 'https://t.me/help' });
});
it('tg:// deep link — валидная схема', () => {
const target = resolveSupportContact(
config({ support_url: 'tg://resolve?domain=help', contact_is_telegram: true }),
);
expect(target).toEqual({ kind: 'telegram', url: 'tg://resolve?domain=help' });
});
describe('чужие схемы в support_url отсекаются', () => {
it.each([
'javascript:alert(1)',
'data:text/html,x',
'not a url',
])('%s -> null', (support_url) => {
expect(resolveSupportContact(config({ support_url }))).toBeNull();
});
});
describe('старый бэк без support_url — прежнее поведение', () => {
it.each([
['@help', 'https://t.me/help'],
['help', 'https://t.me/help'],
])('%s -> %s', (username, expected) => {
const target = resolveSupportContact(config({ support_username: username }));
expect(target).toEqual({ kind: 'telegram', url: expected });
});
it.each([
'https://help.example.com',
'help.example.com',
't.me/help',
])('URL-образный legacy username (%s) не клеится в t.me — null', (support_username) => {
expect(resolveSupportContact(config({ support_username }))).toBeNull();
});
it('контакт вообще не задан — кнопки нет (null, без дефолта @support)', () => {
expect(resolveSupportContact(config({}))).toBeNull();
expect(resolveSupportContact(config({ support_username: ' ' }))).toBeNull();
});
});
it('support_url без contact_is_telegram трактуется как телеграм', () => {
// Комбинация недостижима с текущим бэком, но деградировать надо в старое
// поведение, а не в переход по внешней ссылке.
const target = resolveSupportContact(config({ support_url: 'https://t.me/help' }));
expect(target?.kind).toBe('telegram');
});
});