diff --git a/README.md b/README.md index bee569f..a717cd9 100644 --- a/README.md +++ b/README.md @@ -133,3 +133,26 @@ docker compose up -d mvn test -Dgroups=integration -DexcludedGroups= ``` + +### Allure-отчёт + +Просмотр по умолчанию — по штатному прогону, без живого бэкенда +(`integration`-группа исключена, живого бэкенда в конвейере нет): + +```bash +mvn test +mvn io.qameta.allure:allure-maven:2.17.0:report +# открыть target/allure-report/index.html +``` + +Полный отчёт с live-интеграцией собирается только явным запуском против +локального бэкенда. Требования окружения: поднятый бэкенд на `localhost:8080` +(см. «Запуск с реальным бэкендом» выше) и `.env` с `IT_ADMIN_EMAIL` / +`IT_ADMIN_PASSWORD` сид-админа локального бэкенда (без пароля live-тесты +скипаются): + +```bash +mvn test -Dgroups=integration -DexcludedGroups= +mvn io.qameta.allure:allure-maven:2.17.0:report +# открыть target/allure-report/index.html +``` diff --git a/src/test/java/com/DesktopApplicationClientJava/ui/integration/LoginIntegrationTest.java b/src/test/java/com/DesktopApplicationClientJava/ui/integration/LoginIntegrationTest.java index 9dc3c28..92c710b 100644 --- a/src/test/java/com/DesktopApplicationClientJava/ui/integration/LoginIntegrationTest.java +++ b/src/test/java/com/DesktopApplicationClientJava/ui/integration/LoginIntegrationTest.java @@ -3,6 +3,11 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import com.DesktopApplicationClientJava.services.ServiceException; +import io.qameta.allure.Epic; +import io.qameta.allure.Feature; +import io.qameta.allure.Story; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; /** @@ -13,9 +18,15 @@ * explicitly: {@code mvn test -Dgroups=integration -DexcludedGroups=} ({@code @Tag} inherited from * IntegrationTestCase). */ +@Epic("Проверка ввода") +@Feature("Вход (email + пароль)") +@Story("Страница входа") +@Tag("role:admin") +@DisplayName("Вход через живой API") class LoginIntegrationTest extends IntegrationTestCase { @Test + @DisplayName("Чужие учётные данные отклоняются живым API") void invalidCredentialsAreRejectedByRealApi() { assertThrows( ServiceException.class, diff --git a/src/test/java/com/DesktopApplicationClientJava/ui/integration/ResourceDetailsIntegrationTest.java b/src/test/java/com/DesktopApplicationClientJava/ui/integration/ResourceDetailsIntegrationTest.java index 2fa31ee..fcfbe8b 100644 --- a/src/test/java/com/DesktopApplicationClientJava/ui/integration/ResourceDetailsIntegrationTest.java +++ b/src/test/java/com/DesktopApplicationClientJava/ui/integration/ResourceDetailsIntegrationTest.java @@ -7,7 +7,12 @@ import com.DesktopApplicationClientJava.entities.Resource; import com.DesktopApplicationClientJava.services.ServiceException; +import io.qameta.allure.Epic; +import io.qameta.allure.Feature; +import io.qameta.allure.Story; import java.util.UUID; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; /** @@ -18,13 +23,20 @@ * explicitly: {@code mvn test -Dgroups=integration -DexcludedGroups=} ({@code @Tag} inherited from * IntegrationTestCase). */ +@Epic("Ресурс") +@Feature("Просмотр метаданных ресурса") +@Story("Страница деталей ресурса") +@Tag("role:admin") +@DisplayName("Детали ресурса через живой API") class ResourceDetailsIntegrationTest extends IntegrationTestCase { @Test + @DisplayName("Неизвестный идентификатор бросает сервисную ошибку") void unknownIdThrowsServiceException() { assertThrows(ServiceException.class, () -> resourceService.getById(UUID.randomUUID())); } @Test + @DisplayName("Созданный ресурс читается обратно") void createdResourceRoundTrip() { assumeTrue( adminPassword != null && !adminPassword.isBlank(), diff --git a/src/test/java/com/DesktopApplicationClientJava/ui/integration/ResourceFormIntegrationTest.java b/src/test/java/com/DesktopApplicationClientJava/ui/integration/ResourceFormIntegrationTest.java index 04944f0..4ddb010 100644 --- a/src/test/java/com/DesktopApplicationClientJava/ui/integration/ResourceFormIntegrationTest.java +++ b/src/test/java/com/DesktopApplicationClientJava/ui/integration/ResourceFormIntegrationTest.java @@ -21,6 +21,9 @@ import com.DesktopApplicationClientJava.session.Session; import com.DesktopApplicationClientJava.ui.FxTestBase; import com.DesktopApplicationClientJava.utils.ControllerWiring; +import io.qameta.allure.Epic; +import io.qameta.allure.Feature; +import io.qameta.allure.Story; import java.util.UUID; import javafx.event.ActionEvent; import javafx.fxml.FXMLLoader; @@ -35,6 +38,8 @@ import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; import org.testfx.api.FxRobot; import org.testfx.api.FxToolkit; @@ -52,6 +57,11 @@ * excludedGroups} in pom.xml). Run explicitly: {@code mvn test -Dgroups=integration * -DexcludedGroups= -Dtest=ResourceFormIntegrationTest}. */ +@Epic("Ресурс") +@Feature("Создание и правка ресурса") +@Story("Страница формы ресурса") +@Tag("role:admin") +@DisplayName("Форма ресурса через живой API") class ResourceFormIntegrationTest extends IntegrationTestCase { private final FxRobot robot = new FxRobot(); @@ -99,6 +109,7 @@ void setUpServices() { } @Test + @DisplayName("Создание с корректными полями читается обратно") void createValidRoundTrip() { loginAsAdmin(); String title = "IT ResourceForm " + UUID.randomUUID(); @@ -113,6 +124,7 @@ void createValidRoundTrip() { } @Test + @DisplayName("Создание без описания читается обратно") void createNullDescriptionRoundTrip() { loginAsAdmin(); String title = "IT ResourceForm " + UUID.randomUUID(); @@ -125,6 +137,7 @@ void createNullDescriptionRoundTrip() { } @Test + @DisplayName("Правка названия и описания сохраняется") void updateValidRoundTrip() { loginAsAdmin(); String updatedTitle = "IT ResourceForm updated " + UUID.randomUUID(); @@ -141,6 +154,7 @@ void updateValidRoundTrip() { } @Test + @DisplayName("Некорректные названия отклоняются с 400") void invalidTitlesAreRejectedWith400() { loginAsAdmin(); @@ -150,6 +164,7 @@ void invalidTitlesAreRejectedWith400() { } @Test + @DisplayName("Пустое описание отклоняется с 400") void emptyDescriptionIsRejectedWith400() { loginAsAdmin(); @@ -159,6 +174,7 @@ void emptyDescriptionIsRejectedWith400() { } @Test + @DisplayName("Правка неизвестного ресурса возвращает 404") void updateUnknownIdReturns404() { loginAsAdmin(); @@ -166,6 +182,7 @@ void updateUnknownIdReturns404() { } @Test + @DisplayName("Сохранение с чужими токенами показывает ошибку без перехода") void saveWithInvalidTokensShowsErrorAndDoesNotNavigate() { // Garbage tokens (not a fresh session) force a real HTTP 401 round-trip instead of the // client's offline "no refresh token" shortcut, proving server-side auth enforcement. diff --git a/src/test/java/com/DesktopApplicationClientJava/ui/integration/ResourceListIntegrationTest.java b/src/test/java/com/DesktopApplicationClientJava/ui/integration/ResourceListIntegrationTest.java index add7607..481c97c 100644 --- a/src/test/java/com/DesktopApplicationClientJava/ui/integration/ResourceListIntegrationTest.java +++ b/src/test/java/com/DesktopApplicationClientJava/ui/integration/ResourceListIntegrationTest.java @@ -19,6 +19,9 @@ import com.DesktopApplicationClientJava.session.Session; import com.DesktopApplicationClientJava.ui.FxTestBase; import com.DesktopApplicationClientJava.utils.ControllerWiring; +import io.qameta.allure.Epic; +import io.qameta.allure.Feature; +import io.qameta.allure.Story; import java.util.List; import java.util.UUID; import java.util.concurrent.TimeUnit; @@ -40,6 +43,8 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; import org.testfx.api.FxRobot; import org.testfx.api.FxToolkit; @@ -54,6 +59,10 @@ * the default build and CI (see surefire {@code excludedGroups} in pom.xml). Run explicitly: {@code * mvn test -Dgroups=integration -DexcludedGroups= -Dtest=ResourceListIntegrationTest}. */ +@Epic("Ресурс") +@Feature("Список ресурсов") +@Story("Страница списка ресурсов") +@DisplayName("Список ресурсов через живой API") class ResourceListIntegrationTest extends IntegrationTestCase { private final FxRobot robot = new FxRobot(); @@ -103,6 +112,8 @@ void setUpServices() { } @Test + @Tag("role:admin") + @DisplayName("Пустой поиск показывает заглушку и нулевой счётчик") void emptySearchShowsPlaceholderAndZeroCounter() { loginAsAdmin(); loadListPage(robot); @@ -116,6 +127,8 @@ void emptySearchShowsPlaceholderAndZeroCounter() { } @Test + @Tag("role:admin") + @DisplayName("Созданный ресурс находится поиском") void seededResourceAppearsInSearch() { loginAsAdmin(); String title = "IT ResourceList " + UUID.randomUUID(); @@ -136,6 +149,8 @@ void seededResourceAppearsInSearch() { } @Test + @Tag("role:admin") + @DisplayName("Сортировка по названию упорядочивает ресурсы") void sortByTitleAscOrdersSeededResources() { loginAsAdmin(); String prefix = "IT ResourceSort " + UUID.randomUUID() + " "; @@ -161,6 +176,8 @@ void sortByTitleAscOrdersSeededResources() { } @Test + @Tag("role:admin") + @DisplayName("Поиск без входа показывает ошибку и пустую таблицу") void unauthenticatedSearchShowsErrorAndLeavesTableEmpty() { // Fresh session, no login: the request cannot be authenticated. session.clear(); @@ -182,6 +199,8 @@ void unauthenticatedSearchShowsErrorAndLeavesTableEmpty() { * that lenient contract: unknown sort fields must still return 200 ordered by creation time. */ @Test + @Tag("role:admin") + @DisplayName("Неизвестное поле сортировки откатывается к дате создания") void invalidSortFieldFallsBackToCreatedAtDesc() { loginAsAdmin(); String prefix = "IT SortFallback " + UUID.randomUUID() + " "; @@ -206,6 +225,8 @@ void invalidSortFieldFallsBackToCreatedAtDesc() { * модалку, подтверждение закрывает её и удаляет ресурс на бэкенде. */ @Test + @Tag("role:admin") + @DisplayName("Удаление через модалку убирает ресурс с бэкенда") void deleteCycleRemovesResourceFromBackend() { loginAsAdmin(); String title = "IT ResourceDelete " + UUID.randomUUID(); @@ -240,6 +261,8 @@ void deleteCycleRemovesResourceFromBackend() { } @Test + @Tag("role:moder") + @DisplayName("Удаление глазами модератора") @Disabled("Невоспроизводим: в дев-бэкенде засеян только ADMIN, сида MODER нет") void deleteCycleAsModerator() { // Живой цикл удаления за МОДЕР (не админа) невоспроизводим: интеграционное окружение @@ -255,6 +278,8 @@ private void loginAsAdmin() { } @Test + @Tag("role:admin") + @DisplayName("Поиск по описанию находит ресурс") void searchByDescriptionFindsSeededResource() { loginAsAdmin(); String description = "IT описание " + UUID.randomUUID(); @@ -277,6 +302,8 @@ void searchByDescriptionFindsSeededResource() { } @Test + @Tag("role:admin") + @DisplayName("Отмена удаления оставляет ресурс на бэкенде") void cancelledDeleteKeepsResourceOnBackend() { loginAsAdmin(); String title = "IT ResourceCancel " + UUID.randomUUID();