/
koskv
/
TeamSync-Tests
Обзор
Документация
Войти
/
koskv
/
TeamSync-Tests
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
src/main/java/TeamSyncTest/utils/api/ApiMethodsNew.java
501 строка
21 KB
Koskv
REF: TASK api тесты - закончены
21 ноя 2025, 16:51
21 ноя 2025, 16:51
b07007b
Код
Авторство
О чём код?
package TeamSyncTest.utils.api; import TeamSyncTest.constants.Endpoints; import TeamSyncTest.models.auth.AuthRequest; import TeamSyncTest.models.response.ApiResponse; import com.fasterxml.jackson.databind.ObjectMapper; import io.qameta.allure.Allure; import io.qameta.allure.Step; import io.restassured.RestAssured; import io.restassured.common.mapper.TypeRef; import io.restassured.config.ObjectMapperConfig; import io.restassured.response.Response; import io.restassured.response.ValidatableResponse; import io.restassured.specification.RequestSpecification; import jakarta.annotation.Nullable; import jakarta.annotation.PostConstruct; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.ObjectUtils; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.MediaType; import org.springframework.stereotype.Component; import org.springframework.util.StringUtils; import java.util.List; import java.util.Map; import java.util.Optional; import static TeamSyncTest.constants.HttpStatus.CREATED; import static TeamSyncTest.constants.HttpStatus.OK; import static io.restassured.RestAssured.given; import static org.springframework.http.MediaType.APPLICATION_JSON; @Component @Slf4j @RequiredArgsConstructor public class ApiMethodsNew { private final ObjectMapper objectMapper; private static final String CONTENT_TYPE = HttpHeaders.CONTENT_TYPE; private static final String AUTH = "Authorization"; private static final String BEARER = "Bearer "; @PostConstruct public void init() { RestAssured.config = RestAssured.config() .objectMapperConfig(new ObjectMapperConfig() .jackson2ObjectMapperFactory((cls, charset) -> objectMapper)); log.info("RestAssured настроен с кастомным ObjectMapper"); } protected RequestSpecification createRequest(String authToken) { RequestSpecification request = given() .header(CONTENT_TYPE, APPLICATION_JSON); if (StringUtils.hasText(authToken)) { request.header(AUTH, BEARER + authToken.trim()); } return request; } // ======================================================================== // Универсальный doSend — основа всего // ======================================================================== private <T> ApiResponse<T> doSend( HttpMethod method, String endpoint, @Nullable Object body, @Nullable String contentType, @Nullable String authToken, int expectedStatus, Object mapper, // Class<T> или TypeRef<T> @Nullable String attachmentPrefix ) { String url = Endpoints.BASE_URL + endpoint; contentType = ObjectUtils.defaultIfNull(contentType, MediaType.APPLICATION_JSON_VALUE); RequestSpecification request = given() .header(CONTENT_TYPE, contentType) .log().ifValidationFails(); if (StringUtils.hasText(authToken)) { request.header(AUTH, BEARER + authToken.trim()); } if (body != null) { request.body(body); } ValidatableResponse validatable = request .when().request(method.name(), url) .then().log().ifError(); if (expectedStatus > 0) { validatable.statusCode(expectedStatus); } Response response = validatable.extract().response(); T responseBody = null; if (mapper instanceof Class<?> clazz && clazz != Void.class) { responseBody = response.as((Class<T>) clazz); } else if (mapper instanceof TypeRef<?> typeRef) { responseBody = response.as((TypeRef<T>) typeRef); } String name = attachmentPrefix != null ? attachmentPrefix : method + " " + endpoint; attachToAllure(responseBody, name, endpoint, response.getStatusCode()); return new ApiResponse<>(response.getStatusCode(), responseBody, response); } // ======================================================================== // Универсальные send() // ======================================================================== public <T> ApiResponse<T> send(HttpMethod method, String endpoint, @Nullable Object body, @Nullable String authToken, int expectedStatus, Class<T> responseType, @Nullable String attachmentPrefix) { return doSend(method, endpoint, body, null, authToken, expectedStatus, responseType, attachmentPrefix); } public <T> ApiResponse<T> send(HttpMethod method, String endpoint, @Nullable Object body, @Nullable String authToken, int expectedStatus, TypeRef<T> typeRef, @Nullable String attachmentPrefix) { return doSend(method, endpoint, body, null, authToken, expectedStatus, typeRef, attachmentPrefix); } // ======================================================================== // GET с query-параметрами — обе перегрузки (Class и TypeRef) // ======================================================================== @Step("GET {endpoint} | Query: {queryParams}") public <T> ApiResponse<T> getWithQueryParams( String endpoint, Map<String, ?> queryParams, @Nullable String token, Class<T> responseType, @Nullable String attachmentPrefix ) { return sendWithQueryParams(HttpMethod.GET, endpoint, null, token, queryParams, -1, responseType, attachmentPrefix); } @Step("GET {endpoint} | Query: {queryParams}") public <T> ApiResponse<T> getWithQueryParams( String endpoint, Map<String, ?> queryParams, @Nullable String token, TypeRef<T> typeRef, @Nullable String attachmentPrefix ) { return sendWithQueryParams(HttpMethod.GET, endpoint, null, token, queryParams, -1, typeRef, attachmentPrefix); } // ======================================================================== // === GET со списком + query params (Class версия) === // ======================================================================== @Step("GET {endpoint} → список | Query: {queryParams}") public <T> ApiResponse<List<T>> getListWithQueryParams( String endpoint, Map<String, ?> queryParams, @Nullable String token, Class<T> elementType, @Nullable String attachmentPrefix ) { return sendWithQueryParams( HttpMethod.GET, endpoint, null, token, queryParams, -1, new TypeRef<List<T>>() {}, // важно: анонимный класс! attachmentPrefix ); } // ======================================================================== // === GET со списком + query params (TypeRef версия, если нужны сложные дженерики) === // ======================================================================== @Step("GET {endpoint} → список | Query: {queryParams}") public <T> ApiResponse<List<T>> getListWithQueryParams( String endpoint, Map<String, ?> queryParams, @Nullable String token, TypeRef<List<T>> typeRef, @Nullable String attachmentPrefix ) { return sendWithQueryParams( HttpMethod.GET, endpoint, null, token, queryParams, OK, typeRef, attachmentPrefix ); } // Специально для негативных тестов — когда нужно передать НЕЧИСЛОВОЙ id @Step("GET {service}/{rawId} → ожидаем ошибку из-за формата ID") public <T> ApiResponse<T> getByIdWithRawId( Class<T> responseType, String service, String rawId, // ← именно String, а не Long! @Nullable String token, @Nullable String attachmentPrefix ) { String endpoint = service + "/" + rawId; // подставляем как есть, без парсинга return send( HttpMethod.GET, endpoint, null, token, -1, // статус проверяем в тесте responseType, attachmentPrefix != null ? attachmentPrefix : "GET с некорректным ID: " + rawId ); } @Step("GET {endpoint}/{pathParam} → список") public <T> ApiResponse<T> getWithPathParam( String endpoint, String pathParam, @Nullable String token, TypeRef<T> typeRef, @Nullable String attachmentPrefix ) { return send( HttpMethod.GET, endpoint + "/" + pathParam, null, token, -1, typeRef, attachmentPrefix ); } // ======================================================================== // Любой запрос с query-параметрами (внутренний) // ======================================================================== @Step("{method} {endpoint} | Query: {queryParams}") private <T> ApiResponse<T> sendWithQueryParams( HttpMethod method, String endpoint, @Nullable Object body, @Nullable String authToken, Map<String, ?> queryParams, int expectedStatus, Object mapper, @Nullable String attachmentPrefix ) { String url = Endpoints.BASE_URL + endpoint; RequestSpecification request = given() .header(CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) .log().ifValidationFails(); if (StringUtils.hasText(authToken)) { request.header(AUTH, BEARER + authToken.trim()); } if (body != null) { request.body(body); } if (queryParams != null && !queryParams.isEmpty()) { request.queryParams(queryParams); } ValidatableResponse validatable = request .when().request(method.name(), url) .then().log().ifError(); if (expectedStatus > 0) { validatable.statusCode(expectedStatus); } Response response = validatable.extract().response(); T responseBody = null; if (mapper instanceof Class<?> clazz && clazz != Void.class) { responseBody = response.as((Class<T>) clazz); } else if (mapper instanceof TypeRef<?> typeRef) { responseBody = response.as((TypeRef<T>) typeRef); } String name = attachmentPrefix != null ? attachmentPrefix : "Запрос с query-параметрами"; attachToAllure(responseBody, name, endpoint, response.getStatusCode()); return new ApiResponse<>(response.getStatusCode(), responseBody, response); } // ======================================================================== // POST / PUT с кастомным Content-Type (XML → 415 и т.д.) // ======================================================================== @Step("{method} {endpoint} → {expectedStatus} | Content-Type: {contentType}") public <T> ApiResponse<T> sendWithCustomContentType( HttpMethod method, String endpoint, @Nullable Object body, String contentType, @Nullable String authToken, int expectedStatus, Class<T> responseType, @Nullable String attachmentPrefix ) { return doSend(method, endpoint, body, contentType, authToken, expectedStatus, responseType, attachmentPrefix); } @Step("POST {service} → {expectedStatus} | Content-Type: {contentType}") public <T> ApiResponse<T> postWithCustomContentType( String service, @Nullable Object body, String contentType, @Nullable String authToken, int expectedStatus, Class<T> responseType, @Nullable String attachmentPrefix ) { return sendWithCustomContentType(HttpMethod.POST, service, body, contentType, authToken, expectedStatus, responseType, attachmentPrefix); } @Step("PUT {service}/{id} → {expectedStatus} | Content-Type: {contentType}") public <T> ApiResponse<T> patchWithCustomContentType( String service, Long id, @Nullable Object body, String contentType, @Nullable String authToken, int expectedStatus, Class<T> responseType, @Nullable String attachmentPrefix ) { return sendWithCustomContentType(HttpMethod.PATCH, service + "/" + id, body, contentType, authToken, expectedStatus, responseType, attachmentPrefix); } // ======================================================================== // getAll() // ======================================================================== @Step("GET {service} → список") public <T> List<T> getAll(Class<T> clazz, String service, @Nullable String token) { ApiResponse<List<T>> resp = send( HttpMethod.GET, service, null, token, -1, new TypeRef<List<T>>() {}, "Получение списка" ); return resp.getBody(); } // ======================================================================== // Универсальный метод для негативных тестов — ожидаем ошибку! // ======================================================================== @Step("Ошибка: {method} {endpoint} → {expectedStatus}") public <T> ApiResponse<T> expectError( HttpMethod method, String endpoint, @Nullable Object body, @Nullable String authToken, int expectedStatus, Class<T> errorType ) { return send( method, endpoint, body, authToken, expectedStatus, errorType, "Ожидаемая ошибка " + expectedStatus ); } // Перегрузка без токена (часто используется для auth) @Step("Ошибка: {method} {endpoint} → {expectedStatus}") public <T> ApiResponse<T> expectError( HttpMethod method, String endpoint, @Nullable Object body, int expectedStatus, Class<T> errorType ) { return expectError(method, endpoint, body, null, expectedStatus, errorType); } // ======================================================================== // CRUD-обёртки (JSON по умолчанию) // ======================================================================== @Step("POST {2} → 201") public <T, R> T create(Class<T> clazz, R body, String service, @Nullable String token) { return send(HttpMethod.POST, service, body, token, CREATED, clazz, "Создано").getBody(); } @Step("POST {2} → любой статус") public <T, R> ApiResponse<T> createWithResponse(Class<T> clazz, R body, String service, @Nullable String token) { return send(HttpMethod.POST, service, body, token, -1, clazz, "Создание"); } @Step("POST {2} → {4}") public <T, R> ApiResponse<T> createExpectingError(Class<T> clazz, R body, String service, @Nullable String token, int expectedStatus) { return send(HttpMethod.POST, service, body, token, expectedStatus, clazz, "Ошибка создания"); } @Step("PATCH {2}/{3} → 200") public <T, R> T patch(Class<T> clazz, R body, String service, Long id, @Nullable String token) { return send(HttpMethod.PATCH, service + "/" + id, body, token, OK, clazz, "Частично обновлено").getBody(); } @Step("PATCH {2}/{3} → любой статус") public <T, R> ApiResponse<T> patchWithResponse(Class<T> clazz, R body, String service, Long id, @Nullable String token) { return send(HttpMethod.PATCH, service + "/" + id, body, token, -1, clazz, "Частичное обновление"); } @Step("PATCH {2} → {4}") public <T, R> ApiResponse<T> patchExpectingError(Class<T> clazz, R body, String service, Long id, @Nullable String token, int expectedStatus) { return send(HttpMethod.PATCH, service + "/"+ id, body, token, expectedStatus, clazz, "Ошибка частичного обновления"); } @Step("GET {2}/{3} → любой статус") public <T> ApiResponse<T> getByIdWithResponse(Class<T> clazz, String service, Long id, @Nullable String token) { return send(HttpMethod.GET, service + "/" + id, null, token, -1, clazz, "Получение по ID"); } @Step("GET {service} → список") public <T> List<T> getList(String endpoint, @Nullable String token, TypeRef<List<T>> typeRef) { return getListWithQueryParams(endpoint, Map.of(), token, typeRef, "Список").getBody(); } @Step("DELETE {service}/{id} → {expectedStatus}") public <T> ApiResponse<T> deleteExpectingError( Class<T> errorType, String service, Long id, @Nullable String token, int expectedStatus) { return send(HttpMethod.DELETE, service + "/" + id, null, token, expectedStatus, errorType, "Ошибка удаления"); } @Step("DELETE {1}/{2} → любой статус") public ApiResponse<Void> deleteWithResponse(String service, Long id, @Nullable String token) { return send(HttpMethod.DELETE, service + "/" + id, null, token, -1, Void.class, "Удаление"); } // ======================================================================== // Аутентификация // ======================================================================== public String getToken(String username, String password) { AuthRequest auth = new AuthRequest(username, password); ApiResponse<Map<String, String>> resp = send( HttpMethod.POST, Endpoints.AUTH_API, auth, null, OK, new TypeRef<Map<String, String>>() {}, "Login" ); return Optional.ofNullable(resp.getBody().get("token")) .map(t -> t.startsWith(BEARER) ? t.substring(BEARER.length()).trim() : t) .orElseThrow(() -> new IllegalStateException("Token not received")); } // ======================================================================== // Allure // ======================================================================== private <T> void attachToAllure(T body, String name, String endpoint, int status) { String fullEndpoint = Endpoints.BASE_URL + endpoint; // Если тело null и это DELETE — делаем особую красоту if (body == null && name.toUpperCase().contains("DELETE")) { // Извлекаем ID из endpoint (предполагаем, что он в конце пути) String id = endpoint.substring(endpoint.lastIndexOf("/") + 1); if (id.matches("\\d+")) { String prettyText = """ УДАЛЕНИЕ ЗАПИСИ ID: %s Endpoint: %s Статус: %d %s """.formatted(id, fullEndpoint, status, status == 204 ? "NO_CONTENT (успешно)" : status); Allure.addAttachment("DELETE → ID = " + id, "text/plain", prettyText); return; } } // Для всех остальных случаев — старое поведение if (body == null) { Allure.addAttachment(name, "text/plain", "null (no content)"); return; } try { String json = objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(body); String title = String.format("%s (HTTP %d)\n%s", name, status, fullEndpoint); Allure.addAttachment(title, "application/json", json); } catch (Exception e) { Allure.addAttachment(name + " [PARSE ERROR]", "text/plain", body.toString()); } } }