Унифицированы экраны, требующие авторизации, в гостевом режиме
This commit is contained in:
+24
-32
@@ -1,38 +1,33 @@
|
||||
# Закрыто 21.07-27.07
|
||||
1. MVP frontend: один чат с компанией без истории диалогов; пункт «Чат» открывает текущий активный диалог или создаёт его при отсутствии
|
||||
2. При создании пользователя номер телефона копировать в профиль Russian_Phone
|
||||
3. Убрать хеширование устройства клиента в devise_json - хочу видеть его параметры.
|
||||
4. Добавить параметр, ограничивающих кол-во неуспешных попыток ввода смс.
|
||||
5. Поправить чтение сообщений от Битрикса. Сейчас они выглядят так: "[b]Антон Пичугин:[/b] [br]опять ты?" Надо убрать из текста сообщения Отправителя в битриксе
|
||||
6. Если неавторизованный пользователь вводит сообщение, после отправки идет на регистрацию, после окончания регистрации его сообщение пропадает. Надо чтобы сохранялось и отправлялось (по аналогии с нажатием на кнопку из раздела "Популярные вопросы")
|
||||
7. Изменение в БД по аудитам (заполнение IP, сквозное заполнение UserSession)
|
||||
8. Яндекс.капчу добавить
|
||||
9. Формы согласий поправить (Согласие на обработку ПД + Политика, Пользовательское соглашение, Реклама)
|
||||
10. При повторном запросе OTP кода при авторизации не нужно указывать ошибку "Новый код заказан. Предыдущий код больше не действует."
|
||||
11. Интеграция с СМС-провайдером — спецификация и план rollout зафиксированы в `modules/module-11-idgtl-sms.md`; пункт не закрыт до реализации `sms-service`/worker, Keycloak lifecycle, schema `sms`, callback/nginx, env validation, observability и общего DoD. Production prerequisites: согласованные sender/template, Direct `TOKEN_1`, callback credentials/подтверждённый source IP и статический egress IP.
|
||||
12. Моделирование уведомлений — постановка v6 синхронизирована с `functional_blocks (business logic)/notification-requirements.md`, arch-00…05 и module-01/03/07. Зафиксированы: instruction только в новой вкладке; TTL только при отсутствии `date_expired`; первое скачивание любого связанного документа скрывает; `producer_test` только для smoke, secret/hash раздельно.
|
||||
13. Кнопка "Позвонить оператору" (ссылка tel:+74999591007)
|
||||
14. На главном экране две кнопки: чат и звонок оператору. На кнопке с чатом уведомление при наличии непрочитанных сообщений.
|
||||
## MVP frontend: один чат с компанией без истории диалогов; пункт «Чат» открывает текущий активный диалог или создаёт его при отсутствии
|
||||
## При создании пользователя номер телефона копировать в профиль Russian_Phone
|
||||
## Убрать хеширование устройства клиента в devise_json - хочу видеть его параметры.
|
||||
## Добавить параметр, ограничивающих кол-во неуспешных попыток ввода смс.
|
||||
## Поправить чтение сообщений от Битрикса. Сейчас они выглядят так: "[b]Антон Пичугин:[/b] [br]опять ты?" Надо убрать из текста сообщения Отправителя в битриксе
|
||||
## Если неавторизованный пользователь вводит сообщение, после отправки идет на регистрацию, после окончания регистрации его сообщение пропадает. Надо чтобы сохранялось и отправлялось (по аналогии с нажатием на кнопку из раздела "Популярные вопросы")
|
||||
## Изменение в БД по аудитам (заполнение IP, сквозное заполнение UserSession)
|
||||
## Яндекс.капчу добавить
|
||||
## Формы согласий поправить (Согласие на обработку ПД + Политика, Пользовательское соглашение, Реклама)
|
||||
## При повторном запросе OTP кода при авторизации не нужно указывать ошибку "Новый код заказан. Предыдущий код больше не действует."
|
||||
## Интеграция с СМС-провайдером — спецификация и план rollout зафиксированы в `modules/module-11-idgtl-sms.md`; пункт не закрыт до реализации `sms-service`/worker, Keycloak lifecycle, schema `sms`, callback/nginx, env validation, observability и общего DoD. Production prerequisites: согласованные sender/template, Direct `TOKEN_1`, callback credentials/подтверждённый source IP и статический egress IP.
|
||||
## Моделирование уведомлений — постановка v6 синхронизирована с `functional_blocks (business logic)/notification-requirements.md`, arch-00…05 и module-01/03/07. Зафиксированы: instruction только в новой вкладке; TTL только при отсутствии `date_expired`; первое скачивание любого связанного документа скрывает; `producer_test` только для smoke, secret/hash раздельно.
|
||||
## Кнопка "Позвонить оператору" (ссылка tel:+74999591007)
|
||||
## На главном экране две кнопки: чат и звонок оператору. На кнопке с чатом уведомление при наличии непрочитанных сообщений.
|
||||
|
||||
# Закрыто 28.07-03.08
|
||||
19. Подключить OTLP-провайдер
|
||||
1. Отправлять на UI информацию разные ошибки при попытках авторизации в зависимости от события: код неверен, истёк или уже использован; превышен лимит попыток авторизации, попробуйте через 24 часа (в случаях превышения otp.phone.max_send_attempts_per_24h); превышен лимит неуспешных авторизаций, начните процедуру заново (в случае превышения otp.phone.max_verify_attempts).
|
||||
23. При отрицательном результате проверки сообщения через message-safety, если сообщение отправлялось с главного экрана, то пользователь не переводится в чат, ему под окном главного экрана выпадает сообщение об ошибке. Не на всех устройствах это видно. Воспринимается как UX-дефект. Как надо: вне зависимости от решения message-safety, если пользователь отправил сообщение, то он переводится на экран с чатом. Далее, сейчас отрицательный результат message-safety выводится пользователю как техническая ошибка (красным цветом под полем ввода сообщения) и опять же воспринимается не как бизнес-логика, а как техническая ошибка. Это поведение нужно поменять. Если сообщение пользователя не прошло проверку, нужно ему в окне чата прислать ответ: Для сообщений: К сожалению, ваше сообщение не соответствует правилам данного чата и не может быть отправлено. Попробуйте переформулировать. Для документов: К сожалению, ваш документ не прошел проверку и не может быть доставлен.
|
||||
8. UX-дефект: frontend показывает «Не удалось завершить вход» при ошибке отправки отложенного сообщения, хотя вход завершён. Это следует исправить: завершать экран авторизации после bootstrap, а ошибку Bitrix показывать уже в чате (если сообщение отклонено сервисом message-safety, учесть реализацию предыдущего пункта)
|
||||
22. Ограничить кол-во символов в сообщении на фронте. Показывать в моменте счетчик: n/max, где n сколько символов уже напечатано, max сколько может быть отправлено. Максимальное кол-во символов - положить в app_settings.
|
||||
7. Убрать с экрана ввода номера телефона тексты согласий внизу экрана: Нажимая «Получить код», вы соглашаетесь с условиями использования и политикой конфиденциальности. Согласия пользователь дает ранее на отдельном экране.
|
||||
|
||||
|
||||
## Подключить OTLP-провайдер
|
||||
## Отправлять на UI информацию разные ошибки при попытках авторизации в зависимости от события: код неверен, истёк или уже использован; превышен лимит попыток авторизации, попробуйте через 24 часа (в случаях превышения otp.phone.max_send_attempts_per_24h); превышен лимит неуспешных авторизаций, начните процедуру заново (в случае превышения otp.phone.max_verify_attempts).
|
||||
## При отрицательном результате проверки сообщения через message-safety, если сообщение отправлялось с главного экрана, то пользователь не переводится в чат, ему под окном главного экрана выпадает сообщение об ошибке. Не на всех устройствах это видно. Воспринимается как UX-дефект. Как надо: вне зависимости от решения message-safety, если пользователь отправил сообщение, то он переводится на экран с чатом. Далее, сейчас отрицательный результат message-safety выводится пользователю как техническая ошибка (красным цветом под полем ввода сообщения) и опять же воспринимается не как бизнес-логика, а как техническая ошибка. Это поведение нужно поменять. Если сообщение пользователя не прошло проверку, нужно ему в окне чата прислать ответ: Для сообщений: К сожалению, ваше сообщение не соответствует правилам данного чата и не может быть отправлено. Попробуйте переформулировать. Для документов: К сожалению, ваш документ не прошел проверку и не может быть доставлен.
|
||||
## UX-дефект: frontend показывает «Не удалось завершить вход» при ошибке отправки отложенного сообщения, хотя вход завершён. Это следует исправить: завершать экран авторизации после bootstrap, а ошибку Bitrix показывать уже в чате (если сообщение отклонено сервисом message-safety, учесть реализацию предыдущего пункта)
|
||||
## Ограничить кол-во символов в сообщении на фронте. Показывать в моменте счетчик: n/max, где n сколько символов уже напечатано, max сколько может быть отправлено. Максимальное кол-во символов - положить в app_settings.
|
||||
## Убрать с экрана ввода номера телефона тексты согласий внизу экрана: Нажимая «Получить код», вы соглашаетесь с условиями использования и политикой конфиденциальности. Согласия пользователь дает ранее на отдельном экране.
|
||||
## Перенести секреты из .env в KM Selectel.
|
||||
## Провести аудит безопасности вм
|
||||
## Унифицированы гостевые экраны Центра уведомлений, Профиля и Чата: единый стиль сообщения о необходимости входа и кнопка «Авторизоваться».
|
||||
|
||||
# В разработку:
|
||||
|
||||
3. Унифицировать сообщения гостевого режима о необходмости
|
||||
На экране профиля в гостевом режиме добавить кнопку "Авторизоваться"
|
||||
|
||||
|
||||
2. После интеграции с смс провайдером, реализовать debounce механизм при авторизации - каждая след. смс можно отправить через все большее окно.
|
||||
3. аудит безопасности вм
|
||||
|
||||
5. Store-review вход: точечный bypass в Keycloak OTP SPI по номеру из `.env` (`STORE_REVIEW_ENABLED` / `STORE_REVIEW_PHONE` / `STORE_REVIEW_OTP`) — для этого телефона SMS не шлётся, verify принимает фиксированный OTP; остальные номера идут обычным OTP/SMS. Не путать с глобальным `KEYCLOAK_OTP_MOCK_*`. Учётные данные только в Review Notes стора (не в бинарнике/UI); пользователь с демо-контентом; в production включать только на время ревью.
|
||||
6. Хранить историю устройств, с которых пользователь входил в ЛК (Ид юзера, идентификатор устройства, дата последнего входа, способ входа - веб\приложение)
|
||||
9. Веб-пуши для PWA
|
||||
@@ -40,7 +35,7 @@
|
||||
12. Описание бизнес сущностей: Пользователь
|
||||
12. Описание бизнес сущностей: Сообщение
|
||||
13. Вынести за пределы ВМ1 сервисы message-safety и sync-service.
|
||||
14. Сделать страницу с инстркцией по установке приложения
|
||||
14. Сделать страницу с инструкцией по установке приложения
|
||||
15. Написать пользовательское соглашение.
|
||||
16. Разработка message-safety
|
||||
17. Разработка sync-service
|
||||
@@ -49,7 +44,6 @@
|
||||
21. Спрятать сеть за балансировщиком нагрузки
|
||||
22. Автопродление TLS падает при перезагрузке nginx; сертификат действует до 14.10.2026. (Исправить reload внутри контейнера и проверить systemctl start an-chat-ssl-renew.service до успешного завершения.)
|
||||
23. WireGuard-only SSH.
|
||||
25. Перенести секреты из .env в KMS Selectel.
|
||||
26. Запрет входа под root: В /etc/ssh/sshd_config установите PermitRootLogin no. Заходите под обычным пользователем (например, deploy) и используйте sudo для админских задач.
|
||||
27. Удалите все ненужные пакеты, компиляторы (gcc, make) и сервисы. Чем меньше программ на сервере, тем меньше потенциальных уязвимостей.
|
||||
28. Монтирование с флагами безопасности: Разделы диска (особенно /tmp и /var/tmp) следует монтировать с флагами noexec (запрет запуска исполняемых файлов) и nosuid (игнорирование битов setuid).
|
||||
@@ -58,7 +52,6 @@
|
||||
ProtectSystem=strict # Делает всю ОС доступной только для чтения
|
||||
PrivateTmp=yes # Дает процессу свой изолированный /tmp
|
||||
ProtectHome=yes # Скрывает домашние директории пользователей
|
||||
|
||||
24. Добавить логи (Для Python-сервисов добавить OTLP Log Exporter: api-backend; sms-service; sms-worker. Подключить LoggerProvider, BatchLogRecordProcessor и bounded queue. Передавать resource attributes: service.name; service.version; deployment.environment; service.namespace=han-chat.) Экспортировать структурированные поля request_id, trace_id, span_id, severity и event name. Оставить stdout как аварийный локальный журнал. Добавить canary-тесты, запрещающие экспорт токенов, cookie, телефонов, email, текстов сообщений, SQL и object keys.).
|
||||
25. Nginx metrics/tracing в signoz
|
||||
|
||||
@@ -66,7 +59,6 @@
|
||||
На будущее (после доработки отдельных функциональностей):
|
||||
1. Определение итогового перечня мнемоник, перевод фронтенда на мнемоники, seed заливка мнемоник в БД (?)
|
||||
2. Моделирование профиля клиента.
|
||||
3. Реализация мнемоник.
|
||||
4. Реализовать в полноценном `bitrix-sync` обработчик `document.client_uploaded`: claim/retry/DLQ, идемпотентность по `client_document_id`, группировка по `submission_id`; до этого stub задачи не claim-ит.
|
||||
|
||||
На анализ:
|
||||
|
||||
@@ -5,6 +5,7 @@ import { FlatList, Platform, Text, View } from "react-native";
|
||||
import { useApp } from "../../src/app-context";
|
||||
import { ChatInputBar } from "../../src/components/ChatInputBar";
|
||||
import { ChatScreenHeader } from "../../src/components/ChatScreenHeader";
|
||||
import { GuestAuthGate } from "../../src/components/GuestAuthGate";
|
||||
import { MessageBubble } from "../../src/components/MessageBubble";
|
||||
import { ScreenShell } from "../../src/components/ScreenShell";
|
||||
import { isMessageBlockedError } from "../../src/api";
|
||||
@@ -179,11 +180,12 @@ export default function ChatScreen() {
|
||||
if (app.authStatus !== "authenticated") {
|
||||
return (
|
||||
<ScreenShell>
|
||||
<ChatScreenHeader subtitle="Требуется вход" />
|
||||
<View style={{ flex: 1, padding: spacing.lg, justifyContent: "center" }}>
|
||||
<Text style={styles.error}>Для просмотра чата требуется авторизация.</Text>
|
||||
<Button title="На главную" secondary onPress={() => router.replace("/")} />
|
||||
</View>
|
||||
<ChatScreenHeader title="Чат" subtitle="Требуется вход" />
|
||||
<GuestAuthGate
|
||||
icon="message-circle"
|
||||
title="Чат доступен после входа"
|
||||
description="Авторизуйтесь, чтобы переписываться с оператором и получать ответы."
|
||||
/>
|
||||
</ScreenShell>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { Link, useRouter } from "expo-router";
|
||||
import { useRouter } from "expo-router";
|
||||
import React, { useEffect, useRef } from "react";
|
||||
import { ScrollView, Text, View } from "react-native";
|
||||
import { Text, View } from "react-native";
|
||||
import { useApp } from "../../src/app-context";
|
||||
import { AppHeader } from "../../src/components/AppHeader";
|
||||
import { ChatScreenHeader } from "../../src/components/ChatScreenHeader";
|
||||
import { GuestAuthGate } from "../../src/components/GuestAuthGate";
|
||||
import { ScreenShell } from "../../src/components/ScreenShell";
|
||||
import {
|
||||
bindPendingFileIntent,
|
||||
@@ -37,12 +39,12 @@ export default function DialogsScreen() {
|
||||
if (app.authStatus !== "authenticated") {
|
||||
return (
|
||||
<ScreenShell>
|
||||
<AppHeader guestLabel="Гость" />
|
||||
<View style={{ flex: 1, padding: spacing.lg, justifyContent: "center", gap: spacing.md }}>
|
||||
<Text accessibilityRole="header" style={styles.title}>Чат</Text>
|
||||
<Text style={styles.text}>Чат с компанией доступен после авторизации. Отправьте сообщение на главной странице, чтобы войти.</Text>
|
||||
<Link href="/" style={styles.link}>На главную</Link>
|
||||
</View>
|
||||
<ChatScreenHeader title="Чат" subtitle="Требуется вход" />
|
||||
<GuestAuthGate
|
||||
icon="message-circle"
|
||||
title="Чат доступен после входа"
|
||||
description="Авторизуйтесь, чтобы переписываться с оператором и получать ответы."
|
||||
/>
|
||||
</ScreenShell>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -4,13 +4,14 @@ import { useRouter } from "expo-router";
|
||||
import React, { useMemo, useState } from "react";
|
||||
import { Pressable, ScrollView, StyleSheet, Text, View } from "react-native";
|
||||
import { useApp } from "../../src/app-context";
|
||||
import { GuestAuthGate } from "../../src/components/GuestAuthGate";
|
||||
import { NotificationCard } from "../../src/components/NotificationCard";
|
||||
import { ScreenShell } from "../../src/components/ScreenShell";
|
||||
import { useNotificationAction } from "../../src/notification-actions";
|
||||
import { notificationApi, notificationKeys } from "../../src/notification-api";
|
||||
import { typeMap } from "../../src/notification-presenter";
|
||||
import { colors, radii, spacing } from "../../src/theme";
|
||||
import { Button, ErrorNotice, Loading, styles } from "../../src/ui";
|
||||
import { ErrorNotice, Loading, styles } from "../../src/ui";
|
||||
|
||||
export default function NotificationCenterScreen() {
|
||||
const { authStatus } = useApp();
|
||||
@@ -44,19 +45,11 @@ export default function NotificationCenterScreen() {
|
||||
</View>
|
||||
|
||||
{!authenticated ? (
|
||||
<View style={local.gate}>
|
||||
<View style={local.bell}>
|
||||
<Feather name="bell" size={34} color={colors.primaryForeground} />
|
||||
</View>
|
||||
<Text style={styles.title}>Уведомления доступны после входа</Text>
|
||||
<Text style={[styles.text, local.centerText]}>
|
||||
Авторизуйтесь, чтобы видеть важные напоминания, документы и статусы услуг.
|
||||
</Text>
|
||||
<Button
|
||||
title="Авторизоваться"
|
||||
onPress={() => router.replace({ pathname: "/", params: { authorize: "1" } })}
|
||||
<GuestAuthGate
|
||||
icon="bell"
|
||||
title="Уведомления доступны после входа"
|
||||
description="Авторизуйтесь, чтобы видеть важные напоминания, документы и статусы услуг."
|
||||
/>
|
||||
</View>
|
||||
) : (
|
||||
<ScrollView contentContainerStyle={local.content}>
|
||||
{(notifications.isLoading || catalog.isLoading) && <Loading />}
|
||||
@@ -99,9 +92,6 @@ const local = StyleSheet.create({
|
||||
borderBottomColor: colors.border,
|
||||
},
|
||||
back: { width: 36, height: 36, borderRadius: radii.full, alignItems: "center", justifyContent: "center" },
|
||||
gate: { flex: 1, justifyContent: "center", alignItems: "center", gap: spacing.md, padding: spacing.xl },
|
||||
bell: { width: 68, height: 68, borderRadius: radii.full, alignItems: "center", justifyContent: "center", backgroundColor: colors.primary },
|
||||
centerText: { textAlign: "center" },
|
||||
content: { padding: spacing.lg, gap: spacing.md, paddingBottom: 40 },
|
||||
empty: { alignItems: "center", gap: spacing.sm, paddingVertical: 48 },
|
||||
});
|
||||
|
||||
@@ -5,7 +5,7 @@ import React, { useState } from "react";
|
||||
import { Pressable, ScrollView, StyleSheet, Text, View } from "react-native";
|
||||
import { useApp } from "../src/app-context";
|
||||
import { AccordionSection } from "../src/components/AccordionSection";
|
||||
import { AppHeader } from "../src/components/AppHeader";
|
||||
import { GuestAuthGate } from "../src/components/GuestAuthGate";
|
||||
import { ScreenShell } from "../src/components/ScreenShell";
|
||||
import { isProduction } from "../src/config";
|
||||
import { profileApi } from "../src/services";
|
||||
@@ -30,12 +30,17 @@ export default function ProfileScreen() {
|
||||
if (!enabled) {
|
||||
return (
|
||||
<ScreenShell>
|
||||
<AppHeader guestLabel="Гость" />
|
||||
<View style={{ flex: 1, padding: spacing.lg, justifyContent: "center", gap: spacing.md }}>
|
||||
<View style={stylesLocal.topBar}>
|
||||
<Pressable accessibilityRole="button" accessibilityLabel="Назад" onPress={() => router.back()} style={({ pressed }) => [stylesLocal.backButton, pressed && stylesLocal.pressed]}>
|
||||
<Feather name="arrow-left" size={20} color={colors.foreground} />
|
||||
</Pressable>
|
||||
<Text accessibilityRole="header" style={styles.title}>Профиль</Text>
|
||||
<Text style={styles.text}>Профиль доступен после авторизации.</Text>
|
||||
<Link href="/" style={styles.link}>Перейти на главную для входа</Link>
|
||||
</View>
|
||||
<GuestAuthGate
|
||||
icon="user"
|
||||
title="Профиль доступен после входа"
|
||||
description="Авторизуйтесь, чтобы видеть личные данные и документы компании."
|
||||
/>
|
||||
</ScreenShell>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
import { Feather } from "@expo/vector-icons";
|
||||
import { useRouter } from "expo-router";
|
||||
import React from "react";
|
||||
import { StyleSheet, Text, View } from "react-native";
|
||||
import { colors, radii, spacing } from "../theme";
|
||||
import { Button, styles } from "../ui";
|
||||
|
||||
export function GuestAuthGate({
|
||||
icon,
|
||||
title,
|
||||
description,
|
||||
}: {
|
||||
icon: React.ComponentProps<typeof Feather>["name"];
|
||||
title: string;
|
||||
description: string;
|
||||
}) {
|
||||
const router = useRouter();
|
||||
|
||||
return (
|
||||
<View style={local.gate}>
|
||||
<View style={local.icon}>
|
||||
<Feather name={icon} size={34} color={colors.primaryForeground} />
|
||||
</View>
|
||||
<Text accessibilityRole="header" style={[styles.title, local.centerText]}>{title}</Text>
|
||||
<Text style={[styles.text, local.centerText]}>{description}</Text>
|
||||
<Button
|
||||
title="Авторизоваться"
|
||||
onPress={() => router.replace({ pathname: "/", params: { authorize: "1" } })}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
const local = StyleSheet.create({
|
||||
gate: {
|
||||
flex: 1,
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
gap: spacing.md,
|
||||
padding: spacing.xl,
|
||||
},
|
||||
icon: {
|
||||
width: 68,
|
||||
height: 68,
|
||||
borderRadius: radii.full,
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
backgroundColor: colors.primary,
|
||||
},
|
||||
centerText: { textAlign: "center" },
|
||||
});
|
||||
@@ -67,7 +67,8 @@ test("Колокольчик открывает центр уведомлени
|
||||
await page.getByRole("link", { name: "Уведомления" }).click();
|
||||
await expect(page).toHaveURL(/\/notifications$/);
|
||||
await expect(page.getByRole("heading", { name: "Центр уведомлений" })).toBeVisible();
|
||||
await expect(page.getByText(/доступны после входа/)).toBeVisible();
|
||||
await expect(page.getByRole("heading", { name: "Уведомления доступны после входа" })).toBeVisible();
|
||||
await expect(page.getByRole("button", { name: "Авторизоваться" })).toBeVisible();
|
||||
});
|
||||
|
||||
test("первое сообщение требует обязательные согласия", async ({ page }) => {
|
||||
@@ -99,6 +100,18 @@ test("профиль гостя не делает защищённый запр
|
||||
let protectedCalls = 0;
|
||||
await page.route("**/api/v1/me", (route) => { protectedCalls++; return route.abort(); });
|
||||
await page.goto("/profile");
|
||||
await expect(page.getByText("Профиль доступен после авторизации.")).toBeVisible();
|
||||
await expect(page.getByRole("heading", { name: "Профиль доступен после входа" })).toBeVisible();
|
||||
await expect(page.getByText("Авторизуйтесь, чтобы видеть личные данные и документы компании.")).toBeVisible();
|
||||
await expect(page.getByRole("button", { name: "Авторизоваться" })).toBeVisible();
|
||||
expect(protectedCalls).toBe(0);
|
||||
});
|
||||
|
||||
test("гостевой чат предлагает авторизоваться", async ({ page }) => {
|
||||
await page.goto("/dialogs");
|
||||
await expect(page.getByRole("heading", { name: "Чат доступен после входа" })).toBeVisible();
|
||||
await expect(page.getByText("Авторизуйтесь, чтобы переписываться с оператором и получать ответы.")).toBeVisible();
|
||||
|
||||
await page.getByRole("button", { name: "Авторизоваться" }).click();
|
||||
await expect(page).toHaveURL(/\/\?authorize=1$/);
|
||||
await expect(page.getByRole("heading", { name: "Перед началом работы" })).toBeVisible();
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user