From 3d722481f107a33c39ed4c1b501e21dadb8da638 Mon Sep 17 00:00:00 2001 From: Evgeniy Ivanov Date: Wed, 24 Jun 2026 08:53:57 +0400 Subject: [PATCH 1/3] YH-2143: Adding mock error handling when creating a theme --- .../__mocks__/helpers/getMockAuthProfile.ts | 7 ++ src/entities/auth/index.ts | 1 + .../__mock__/createSpecializationErrorMock.ts | 17 ---- .../createTopics/api/__mock__/createTopic.ts | 34 -------- .../api/__mock__/createTopicMock.ts | 78 +++++++++++++++++++ src/features/topics/createTopics/index.ts | 2 +- .../model/types/topicCreateErrorTypes.ts | 3 +- .../libs/query/helpers/createErrorResponse.ts | 11 +++ src/shared/libs/query/index.ts | 1 + 9 files changed, 101 insertions(+), 53 deletions(-) create mode 100644 src/entities/auth/api/__mocks__/helpers/getMockAuthProfile.ts delete mode 100644 src/features/specialization/createSpecialization/api/__mock__/createSpecializationErrorMock.ts delete mode 100644 src/features/topics/createTopics/api/__mock__/createTopic.ts create mode 100644 src/features/topics/createTopics/api/__mock__/createTopicMock.ts create mode 100644 src/shared/libs/query/helpers/createErrorResponse.ts diff --git a/src/entities/auth/api/__mocks__/helpers/getMockAuthProfile.ts b/src/entities/auth/api/__mocks__/helpers/getMockAuthProfile.ts new file mode 100644 index 000000000..0dfdb92c0 --- /dev/null +++ b/src/entities/auth/api/__mocks__/helpers/getMockAuthProfile.ts @@ -0,0 +1,7 @@ +import { authMockProfilesByAccessToken } from '../data'; + +export const getMockAuthProfile = (request: Request) => { + const authorizationHeader = request.headers.get('Authorization') ?? ''; + const accessToken = authorizationHeader.replace(/^Bearer\s+/i, ''); + return authMockProfilesByAccessToken[accessToken]; +}; diff --git a/src/entities/auth/index.ts b/src/entities/auth/index.ts index 129b9777f..6272a9e48 100644 --- a/src/entities/auth/index.ts +++ b/src/entities/auth/index.ts @@ -23,6 +23,7 @@ export { useLazyRefreshQuery, } from './api/authApi'; export { authHandlers } from './api/__mocks__'; +export { getMockAuthProfile } from './api/__mocks__/helpers/getMockAuthProfile'; export { refreshMiddleware } from './api/refreshMiddleware'; export { RegistrationLabel } from './ui/RegistrationLabel/RegistrationLabel'; export { TelegramWidget } from './ui/TelegramWidget/TelegramWidget'; diff --git a/src/features/specialization/createSpecialization/api/__mock__/createSpecializationErrorMock.ts b/src/features/specialization/createSpecialization/api/__mock__/createSpecializationErrorMock.ts deleted file mode 100644 index 45f9a114c..000000000 --- a/src/features/specialization/createSpecialization/api/__mock__/createSpecializationErrorMock.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { http, HttpResponse } from 'msw'; - -import { createSpecializationApiUrls } from '../../model/constants/createSpecializationConstants'; - -export const createSpecializationErrorMock = http.post( - process.env.API_URL + createSpecializationApiUrls.createSpecialization, - async () => { - return HttpResponse.json( - { - message: 'auth.user.verified', - statusCode: 403, - description: 'Route is available for verified users!', - }, - { status: 403 }, - ); - }, -); diff --git a/src/features/topics/createTopics/api/__mock__/createTopic.ts b/src/features/topics/createTopics/api/__mock__/createTopic.ts deleted file mode 100644 index 5801fe206..000000000 --- a/src/features/topics/createTopics/api/__mock__/createTopic.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { http, HttpResponse } from 'msw'; - -import { skillsMock } from '@/entities/skill'; -import { topicsMocks } from '@/entities/topic'; - -import { createTopicApiUrls } from '../../model/constants/createTopicConstants'; -import { CreateTopicBodyRequest, CreateTopicResponse } from '../../model/types/topicCreateTypes'; - -export const createTopicMock = http.post( - process.env.API_URL + createTopicApiUrls.createTopic, - async ({ request }) => { - const topic: CreateTopicBodyRequest = await request.json(); - - const response: CreateTopicResponse = { - id: Date.now(), - title: topic.title, - description: topic.description, - imageSrc: null, - createdAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), - skill: skillsMock.data.find((skill) => skill.id === topic.skillId) ?? { - id: topic.skillId, - title: '', - description: '', - imageSrc: null, - createdAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), - }, - }; - - topicsMocks.data.push(response); - return HttpResponse.json(response); - }, -); diff --git a/src/features/topics/createTopics/api/__mock__/createTopicMock.ts b/src/features/topics/createTopics/api/__mock__/createTopicMock.ts new file mode 100644 index 000000000..02a8bdad0 --- /dev/null +++ b/src/features/topics/createTopics/api/__mock__/createTopicMock.ts @@ -0,0 +1,78 @@ +import { http, HttpResponse } from 'msw'; + +import { createErrorResponse } from '@/shared/libs'; + +import { getMockAuthProfile, listAdminRoles } from '@/entities/auth'; +import { skillsMock } from '@/entities/skill'; +import { topicsMocks } from '@/entities/topic'; + +import { createTopicApiUrls } from '../../model/constants/createTopicConstants'; +import { TopicCreateError } from '../../model/types/topicCreateErrorTypes'; +import { CreateTopicBodyRequest, CreateTopicResponse } from '../../model/types/topicCreateTypes'; + +export const createTopicMock = http.post< + never, + CreateTopicBodyRequest, + CreateTopicResponse | ApiErrorData +>(process.env.API_URL + createTopicApiUrls.createTopic, async ({ request }) => { + const profileMockResponse = getMockAuthProfile(request); + + if (!profileMockResponse) { + const { error, option } = createErrorResponse( + 'auth.auth.unauthorized', + 401, + 'Authentication failed', + ); + return HttpResponse.json(error, option); + } + if (!profileMockResponse.isVerified) { + const { error, option } = createErrorResponse( + 'auth.user.verified', + 403, + 'Route is available for verified users!', + ); + return HttpResponse.json(error, option); + } + + if (!profileMockResponse.userRoles.some((role) => listAdminRoles.includes(role.name))) { + const { error, option } = createErrorResponse( + 'auth.roles.admin_or_author_required', + 403, + 'Admin or author required', + ); + return HttpResponse.json(error, option); + } + + const topic: CreateTopicBodyRequest = await request.json(); + + if (topicsMocks.data.some((t) => t.title === topic.title)) { + const { error, option } = createErrorResponse( + 'topic.topic.title.conflict', + 409, + 'A topic with the same title already exists for this skill', + ); + return HttpResponse.json(error, option); + } + + const date = new Date().toISOString(); + + const response: CreateTopicResponse = { + id: Date.now(), + title: topic.title, + description: topic.description, + imageSrc: null, + createdAt: date, + updatedAt: date, + skill: skillsMock.data.find((skill) => skill.id === topic.skillId) ?? { + id: topic.skillId, + title: '', + description: '', + imageSrc: null, + createdAt: date, + updatedAt: date, + }, + }; + + topicsMocks.data.push(response); + return HttpResponse.json(response); +}); diff --git a/src/features/topics/createTopics/index.ts b/src/features/topics/createTopics/index.ts index 0d15e9e94..6fb960f7e 100644 --- a/src/features/topics/createTopics/index.ts +++ b/src/features/topics/createTopics/index.ts @@ -1,4 +1,4 @@ -import { createTopicMock } from './api/__mock__/createTopic'; +import { createTopicMock } from './api/__mock__/createTopicMock'; export { useCreateTopicMutation } from './api/createTopicApi'; export { TopicCreateForm } from './ui/TopicCreateForm/TopicCreateForm'; diff --git a/src/features/topics/createTopics/model/types/topicCreateErrorTypes.ts b/src/features/topics/createTopics/model/types/topicCreateErrorTypes.ts index 94c3af2f3..606196f90 100644 --- a/src/features/topics/createTopics/model/types/topicCreateErrorTypes.ts +++ b/src/features/topics/createTopics/model/types/topicCreateErrorTypes.ts @@ -5,4 +5,5 @@ export type TopicCreateError = | 'topic.topic.title.conflict' | 'tinify.tinify.compress_failed' | 'tinify.tinify.resize_failed' - | 'toast.topics.create.failed'; + | 'toast.topics.create.failed' + | 'auth.roles.admin_or_author_required'; diff --git a/src/shared/libs/query/helpers/createErrorResponse.ts b/src/shared/libs/query/helpers/createErrorResponse.ts new file mode 100644 index 000000000..c7524f771 --- /dev/null +++ b/src/shared/libs/query/helpers/createErrorResponse.ts @@ -0,0 +1,11 @@ +export const createErrorResponse = (message: T, statusCode: number, description: string) => { + const error = { + message, + statusCode, + description, + }; + const option = { + status: statusCode, + }; + return { error, option }; +}; diff --git a/src/shared/libs/query/index.ts b/src/shared/libs/query/index.ts index 876c497f4..6685e314d 100644 --- a/src/shared/libs/query/index.ts +++ b/src/shared/libs/query/index.ts @@ -1,5 +1,6 @@ export { route } from './route'; export { useQueryFilterParams } from './useQueryFilterParams'; export { handleApiError } from './handleApiError'; +export { createErrorResponse } from './helpers/createErrorResponse'; export type { Response } from './types'; export { LS_ACCESS_TOKEN_KEY } from './authConstants'; From cba5c3007523fdbcd05669434d60ca28edf37631 Mon Sep 17 00:00:00 2001 From: Evgeniy Ivanov Date: Wed, 1 Jul 2026 14:44:12 +0400 Subject: [PATCH 2/3] YH-2143: Adding mock error handling when creating a theme --- public/locales/en/translation.json | 3 ++ public/locales/ru/translation.json | 3 ++ .../api/__mock__/createTopicMock.ts | 50 +++++++++++-------- .../utils/getCreateTopicsApiErrorMessage.ts | 3 ++ src/shared/config/i18n/i18nTranslations.ts | 1 + .../libs/query/helpers/createErrorResponse.ts | 11 ---- src/shared/libs/query/index.ts | 1 - 7 files changed, 38 insertions(+), 34 deletions(-) delete mode 100644 src/shared/libs/query/helpers/createErrorResponse.ts diff --git a/public/locales/en/translation.json b/public/locales/en/translation.json index f68c9a8e3..5074a6c5d 100644 --- a/public/locales/en/translation.json +++ b/public/locales/en/translation.json @@ -529,6 +529,9 @@ "verified": "Route is available for verified users!" } }, + "rightsAcsses": { + "conflict": "Admin or author required" + }, "topic": { "skill": { "not_found": "Skill not found" diff --git a/public/locales/ru/translation.json b/public/locales/ru/translation.json index 67b8e30aa..9e9e3842c 100644 --- a/public/locales/ru/translation.json +++ b/public/locales/ru/translation.json @@ -532,6 +532,9 @@ "verified": "Доступно только для верифицированных пользователей" } }, + "rightsAcsses": { + "conflict": "Требуются права администратора или автора" + }, "topic": { "skill": { "not_found": "Навык не найден" diff --git a/src/features/topics/createTopics/api/__mock__/createTopicMock.ts b/src/features/topics/createTopics/api/__mock__/createTopicMock.ts index 02a8bdad0..4641205bd 100644 --- a/src/features/topics/createTopics/api/__mock__/createTopicMock.ts +++ b/src/features/topics/createTopics/api/__mock__/createTopicMock.ts @@ -1,7 +1,5 @@ import { http, HttpResponse } from 'msw'; -import { createErrorResponse } from '@/shared/libs'; - import { getMockAuthProfile, listAdminRoles } from '@/entities/auth'; import { skillsMock } from '@/entities/skill'; import { topicsMocks } from '@/entities/topic'; @@ -18,40 +16,48 @@ export const createTopicMock = http.post< const profileMockResponse = getMockAuthProfile(request); if (!profileMockResponse) { - const { error, option } = createErrorResponse( - 'auth.auth.unauthorized', - 401, - 'Authentication failed', + return HttpResponse.json( + { + message: 'auth.auth.unauthorized', + statusCode: 401, + description: 'Authentication failed', + }, + { status: 401 }, ); - return HttpResponse.json(error, option); } if (!profileMockResponse.isVerified) { - const { error, option } = createErrorResponse( - 'auth.user.verified', - 403, - 'Route is available for verified users!', + return HttpResponse.json( + { + message: 'auth.user.verified', + statusCode: 403, + description: 'Route is available for verified users!', + }, + { status: 403 }, ); - return HttpResponse.json(error, option); } if (!profileMockResponse.userRoles.some((role) => listAdminRoles.includes(role.name))) { - const { error, option } = createErrorResponse( - 'auth.roles.admin_or_author_required', - 403, - 'Admin or author required', + return HttpResponse.json( + { + message: 'auth.roles.admin_or_author_required', + statusCode: 403, + description: 'Admin or author required', + }, + { status: 403 }, ); - return HttpResponse.json(error, option); } const topic: CreateTopicBodyRequest = await request.json(); if (topicsMocks.data.some((t) => t.title === topic.title)) { - const { error, option } = createErrorResponse( - 'topic.topic.title.conflict', - 409, - 'A topic with the same title already exists for this skill', + return HttpResponse.json( + { + message: 'topic.topic.title.conflict', + statusCode: 409, + description: 'A topic with the same title already exists for this skill', + }, + { status: 409 }, ); - return HttpResponse.json(error, option); } const date = new Date().toISOString(); diff --git a/src/features/topics/createTopics/lib/utils/getCreateTopicsApiErrorMessage.ts b/src/features/topics/createTopics/lib/utils/getCreateTopicsApiErrorMessage.ts index 4a0b4d277..a1e1ac39a 100644 --- a/src/features/topics/createTopics/lib/utils/getCreateTopicsApiErrorMessage.ts +++ b/src/features/topics/createTopics/lib/utils/getCreateTopicsApiErrorMessage.ts @@ -7,6 +7,9 @@ export const getCreateTopicsApiErrorMessage = (error: ApiErrorData(message: T, statusCode: number, description: string) => { - const error = { - message, - statusCode, - description, - }; - const option = { - status: statusCode, - }; - return { error, option }; -}; diff --git a/src/shared/libs/query/index.ts b/src/shared/libs/query/index.ts index 6685e314d..876c497f4 100644 --- a/src/shared/libs/query/index.ts +++ b/src/shared/libs/query/index.ts @@ -1,6 +1,5 @@ export { route } from './route'; export { useQueryFilterParams } from './useQueryFilterParams'; export { handleApiError } from './handleApiError'; -export { createErrorResponse } from './helpers/createErrorResponse'; export type { Response } from './types'; export { LS_ACCESS_TOKEN_KEY } from './authConstants'; From 35dce5d04b9057e468cae04fe020434e892330e3 Mon Sep 17 00:00:00 2001 From: Evgeniy Ivanov Date: Wed, 1 Jul 2026 16:59:16 +0400 Subject: [PATCH 3/3] YH-2143: Standardizing the process of adding skills --- .../topics/createTopics/api/__mock__/createTopicMock.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/features/topics/createTopics/api/__mock__/createTopicMock.ts b/src/features/topics/createTopics/api/__mock__/createTopicMock.ts index 4641205bd..c2f72549b 100644 --- a/src/features/topics/createTopics/api/__mock__/createTopicMock.ts +++ b/src/features/topics/createTopics/api/__mock__/createTopicMock.ts @@ -49,7 +49,7 @@ export const createTopicMock = http.post< const topic: CreateTopicBodyRequest = await request.json(); - if (topicsMocks.data.some((t) => t.title === topic.title)) { + if (topicsMocks.data.some((t) => t.title === topic.title && t.skill.id === topic.skillId)) { return HttpResponse.json( { message: 'topic.topic.title.conflict', @@ -69,13 +69,15 @@ export const createTopicMock = http.post< imageSrc: null, createdAt: date, updatedAt: date, - skill: skillsMock.data.find((skill) => skill.id === topic.skillId) ?? { + skill: skillsMock.find((skill) => skill.id === topic.skillId) ?? { id: topic.skillId, title: '', description: '', imageSrc: null, createdAt: date, updatedAt: date, + specializations: [], + createdBy: { id: '', username: '' }, }, };