diff --git a/pom.xml b/pom.xml index 19e4e2e..959f179 100644 --- a/pom.xml +++ b/pom.xml @@ -185,6 +185,18 @@ false + + exim-v1 + + generate + + + src/main/openapi/onecx-theme-exim-v1-openapi.yaml + gen.io.github.onecx.theme.rs.exim.v1 + gen.io.github.onecx.theme.rs.exim.v1.model + DTOV1 + + diff --git a/src/main/java/io/github/onecx/theme/domain/daos/ThemeDAO.java b/src/main/java/io/github/onecx/theme/domain/daos/ThemeDAO.java index db59779..cca50ba 100644 --- a/src/main/java/io/github/onecx/theme/domain/daos/ThemeDAO.java +++ b/src/main/java/io/github/onecx/theme/domain/daos/ThemeDAO.java @@ -1,5 +1,8 @@ package io.github.onecx.theme.domain.daos; +import java.util.Set; +import java.util.stream.Stream; + import jakarta.enterprise.context.ApplicationScoped; import jakarta.persistence.NoResultException; import jakarta.transaction.Transactional; @@ -11,9 +14,27 @@ import io.github.onecx.theme.domain.models.Theme_; @ApplicationScoped +@Transactional(Transactional.TxType.NOT_SUPPORTED) public class ThemeDAO extends AbstractDAO { @Transactional(Transactional.TxType.SUPPORTS) + public Stream findThemeByNames(Set themeNames) { + try { + var cb = this.getEntityManager().getCriteriaBuilder(); + var cq = cb.createQuery(Theme.class); + var root = cq.from(Theme.class); + + if (themeNames != null && !themeNames.isEmpty()) { + cq.where(root.get(Theme_.name).in(themeNames)); + } + + return this.getEntityManager().createQuery(cq).getResultStream(); + + } catch (Exception ex) { + throw new DAOException(ErrorKeys.ERROR_FIND_THEME_BY_NAMES, ex); + } + } + public Theme findThemeByName(String themeName) { try { var cb = this.getEntityManager().getCriteriaBuilder(); @@ -21,7 +42,7 @@ public Theme findThemeByName(String themeName) { var root = cq.from(Theme.class); cq.where(cb.equal(root.get(Theme_.name), themeName)); - return this.em.createQuery(cq).getSingleResult(); + return this.getEntityManager().createQuery(cq).getSingleResult(); } catch (NoResultException nre) { return null; @@ -31,6 +52,8 @@ public Theme findThemeByName(String themeName) { } public enum ErrorKeys { + + ERROR_FIND_THEME_BY_NAMES, ERROR_FIND_THEME_BY_NAME, } } diff --git a/src/main/java/io/github/onecx/theme/domain/di/mappers/DataImportMapperV1.java b/src/main/java/io/github/onecx/theme/domain/di/mappers/DataImportMapperV1.java index 4ad5e6e..18b13b0 100644 --- a/src/main/java/io/github/onecx/theme/domain/di/mappers/DataImportMapperV1.java +++ b/src/main/java/io/github/onecx/theme/domain/di/mappers/DataImportMapperV1.java @@ -1,10 +1,10 @@ package io.github.onecx.theme.domain.di.mappers; +import java.util.ArrayList; import java.util.List; import jakarta.inject.Inject; -import org.mapstruct.IterableMapping; import org.mapstruct.Mapper; import org.mapstruct.Mapping; import org.mapstruct.Named; @@ -14,6 +14,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import gen.io.github.onecx.theme.di.v1.model.DataImportThemeDTOV1; +import gen.io.github.onecx.theme.di.v1.model.DataImportThemesDTOV1; import io.github.onecx.theme.domain.models.Theme; @Mapper(uses = OffsetDateTimeMapper.class) @@ -22,11 +23,23 @@ public abstract class DataImportMapperV1 { @Inject ObjectMapper mapper; - @IterableMapping(qualifiedByName = "import") - public abstract List importThemes(List dto); + public List importThemes(DataImportThemesDTOV1 request) { + List result = new ArrayList<>(); + if (request == null) { + return result; + } + request.forEach((name, dto) -> { + var theme = importTheme(dto); + theme.setName(name); + result.add(theme); + }); + + return result; + } @Named("import") @Mapping(target = "id", ignore = true) + @Mapping(target = "name", ignore = true) @Mapping(target = "properties", qualifiedByName = "properties") @Mapping(target = "creationDate", ignore = true) @Mapping(target = "creationUser", ignore = true) @@ -35,7 +48,7 @@ public abstract class DataImportMapperV1 { @Mapping(target = "controlTraceabilityManual", ignore = true) @Mapping(target = "modificationCount", ignore = true) @Mapping(target = "persisted", ignore = true) - public abstract Theme theme(DataImportThemeDTOV1 dto); + public abstract Theme importTheme(DataImportThemeDTOV1 dto); @Named("properties") String properties(Object properties) { diff --git a/src/main/java/io/github/onecx/theme/rs/exim/v1/controllers/ExportImportRestControllerV1.java b/src/main/java/io/github/onecx/theme/rs/exim/v1/controllers/ExportImportRestControllerV1.java new file mode 100644 index 0000000..daaa93a --- /dev/null +++ b/src/main/java/io/github/onecx/theme/rs/exim/v1/controllers/ExportImportRestControllerV1.java @@ -0,0 +1,86 @@ +package io.github.onecx.theme.rs.exim.v1.controllers; + +import java.util.stream.Collectors; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import jakarta.transaction.Transactional; +import jakarta.validation.ConstraintViolationException; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.core.Response; + +import org.jboss.resteasy.reactive.RestResponse; +import org.jboss.resteasy.reactive.server.ServerExceptionMapper; + +import gen.io.github.onecx.theme.rs.exim.v1.ThemesExportImportApi; +import gen.io.github.onecx.theme.rs.exim.v1.model.*; +import io.github.onecx.theme.domain.daos.ThemeDAO; +import io.github.onecx.theme.domain.models.Theme; +import io.github.onecx.theme.rs.exim.v1.mappers.ExportImportExceptionMapperV1; +import io.github.onecx.theme.rs.exim.v1.mappers.ExportImportMapperV1; + +@Path("/exim/v1/themes") +@ApplicationScoped +@Transactional(Transactional.TxType.NOT_SUPPORTED) +public class ExportImportRestControllerV1 implements ThemesExportImportApi { + + @Inject + ThemeDAO dao; + @Inject + ExportImportExceptionMapperV1 exceptionMapper; + + @Inject + ExportImportMapperV1 mapper; + + @Override + public Response exportThemes(EximExportRequestDTOV1 request) { + var themes = dao.findThemeByNames(request.getNames()); + + var data = themes.collect(Collectors.toMap(Theme::getName, theme -> theme)); + + if (data.isEmpty()) { + return Response.status(Response.Status.NOT_FOUND).build(); + } + return Response.ok(mapper.create(data)).build(); + } + + @Override + @Transactional(Transactional.TxType.REQUIRES_NEW) + public Response importThemes(EximImportRequestDTOV1 request) { + var keys = request.getThemes().keySet(); + var themes = dao.findThemeByNames(keys); + var map = themes.collect(Collectors.toMap(Theme::getName, theme -> theme)); + + EximImportResultThemesDTOV1 items = new EximImportResultThemesDTOV1(); + + request.getThemes().forEach((name, dto) -> { + + var theme = map.get(name); + if (theme == null) { + + theme = mapper.create(dto); + theme.setName(name); + dao.create(theme); + items.put(name, mapper.create(EximThemeResultStatusDTOV1.CREATED)); + + } else { + + mapper.update(dto, theme); + dao.update(theme); + items.put(name, mapper.create(EximThemeResultStatusDTOV1.UPDATE)); + } + }); + + return Response.ok(mapper.create(request, items)).build(); + } + + @ServerExceptionMapper + public RestResponse exception(Exception ex) { + return exceptionMapper.exception(ex); + } + + @ServerExceptionMapper + public RestResponse constraint(ConstraintViolationException ex) { + return exceptionMapper.constraint(ex); + } +} diff --git a/src/main/java/io/github/onecx/theme/rs/exim/v1/mappers/ExportImportExceptionMapperV1.java b/src/main/java/io/github/onecx/theme/rs/exim/v1/mappers/ExportImportExceptionMapperV1.java new file mode 100644 index 0000000..433d152 --- /dev/null +++ b/src/main/java/io/github/onecx/theme/rs/exim/v1/mappers/ExportImportExceptionMapperV1.java @@ -0,0 +1,69 @@ +package io.github.onecx.theme.rs.exim.v1.mappers; + +import java.util.List; +import java.util.Set; + +import jakarta.validation.ConstraintViolation; +import jakarta.validation.ConstraintViolationException; +import jakarta.validation.Path; +import jakarta.ws.rs.core.Response; + +import org.jboss.resteasy.reactive.RestResponse; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.tkit.quarkus.jpa.exceptions.DAOException; +import org.tkit.quarkus.rs.mappers.OffsetDateTimeMapper; + +import gen.io.github.onecx.theme.rs.exim.v1.model.*; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@Mapper(uses = { OffsetDateTimeMapper.class }) +public abstract class ExportImportExceptionMapperV1 { + + public RestResponse constraint(ConstraintViolationException ex) { + log.error("Processing theme export import rest controller error: {}", ex.getMessage()); + + var dto = exception("CONSTRAINT_VIOLATIONS", ex.getMessage()); + dto.setValidations(createErrorValidationResponse(ex.getConstraintViolations())); + return RestResponse.status(Response.Status.BAD_REQUEST, dto); + } + + public RestResponse exception(Exception ex) { + log.error("Processing theme export import rest controller error: {}", ex.getMessage()); + + if (ex instanceof DAOException de) { + return RestResponse.status(Response.Status.BAD_REQUEST, + exception(de.getMessageKey().name(), ex.getMessage(), de.parameters)); + } + return RestResponse.status(Response.Status.INTERNAL_SERVER_ERROR, + exception("UNDEFINED_ERROR_CODE", ex.getMessage())); + + } + + @Mapping(target = "removeParametersItem", ignore = true) + @Mapping(target = "namedParameters", ignore = true) + @Mapping(target = "removeNamedParametersItem", ignore = true) + @Mapping(target = "parameters", ignore = true) + @Mapping(target = "validations", ignore = true) + @Mapping(target = "removeValidationsItem", ignore = true) + public abstract EximRestExceptionDTOV1 exception(String errorCode, String message); + + @Mapping(target = "removeParametersItem", ignore = true) + @Mapping(target = "namedParameters", ignore = true) + @Mapping(target = "removeNamedParametersItem", ignore = true) + @Mapping(target = "validations", ignore = true) + @Mapping(target = "removeValidationsItem", ignore = true) + public abstract EximRestExceptionDTOV1 exception(String errorCode, String message, List parameters); + + public abstract List createErrorValidationResponse( + Set> constraintViolation); + + @Mapping(target = "parameter", source = "propertyPath") + @Mapping(target = "message", source = "message") + public abstract EximValidationConstraintDTOV1 createError(ConstraintViolation constraintViolation); + + public String mapPath(Path path) { + return path.toString(); + } +} diff --git a/src/main/java/io/github/onecx/theme/rs/exim/v1/mappers/ExportImportMapperV1.java b/src/main/java/io/github/onecx/theme/rs/exim/v1/mappers/ExportImportMapperV1.java new file mode 100644 index 0000000..125bd85 --- /dev/null +++ b/src/main/java/io/github/onecx/theme/rs/exim/v1/mappers/ExportImportMapperV1.java @@ -0,0 +1,83 @@ +package io.github.onecx.theme.rs.exim.v1.mappers; + +import java.time.OffsetDateTime; +import java.util.Map; + +import jakarta.inject.Inject; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.MappingTarget; +import org.mapstruct.Named; +import org.tkit.quarkus.rs.mappers.OffsetDateTimeMapper; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; + +import gen.io.github.onecx.theme.rs.exim.v1.model.*; +import io.github.onecx.theme.domain.models.Theme; + +@Mapper(uses = { OffsetDateTimeMapper.class }) +public abstract class ExportImportMapperV1 { + + @Inject + ObjectMapper objectMapper; + + @Mapping(target = "id", source = "request.id") + @Mapping(target = "themes", source = "themes") + public abstract EximImportResultDTOV1 create(EximImportRequestDTOV1 request, EximImportResultThemesDTOV1 themes); + + public abstract EximThemeResultDTOV1 create(EximThemeResultStatusDTOV1 status); + + public EximImportRequestDTOV1 create(Map data) { + if (data == null) { + return null; + } + EximImportRequestDTOV1 result = new EximImportRequestDTOV1(); + result.setCreated(OffsetDateTime.now()); + result.setThemes(map(data)); + return result; + } + + public abstract EximImportThemesDTOV1 map(Map data); + + public abstract EximThemeDTOV1 map(Theme theme); + + @Mapping(target = "id", ignore = true) + @Mapping(target = "name", ignore = true) + @Mapping(target = "creationDate", ignore = true) + @Mapping(target = "creationUser", ignore = true) + @Mapping(target = "modificationDate", ignore = true) + @Mapping(target = "modificationUser", ignore = true) + @Mapping(target = "controlTraceabilityManual", ignore = true) + @Mapping(target = "modificationCount", ignore = true) + @Mapping(target = "persisted", ignore = true) + @Mapping(target = "properties", qualifiedByName = "properties") + public abstract void update(EximThemeDTOV1 dto, @MappingTarget Theme entity); + + @Mapping(target = "id", ignore = true) + @Mapping(target = "name", ignore = true) + @Mapping(target = "creationDate", ignore = true) + @Mapping(target = "creationUser", ignore = true) + @Mapping(target = "modificationDate", ignore = true) + @Mapping(target = "modificationUser", ignore = true) + @Mapping(target = "controlTraceabilityManual", ignore = true) + @Mapping(target = "modificationCount", ignore = true) + @Mapping(target = "persisted", ignore = true) + @Mapping(target = "properties", qualifiedByName = "properties") + public abstract Theme create(EximThemeDTOV1 dto); + + @Named("properties") + public String mapToString(Object properties) { + + if (properties == null) { + return null; + } + + try { + return objectMapper.writeValueAsString(properties); + } catch (JsonProcessingException e) { + return null; + } + } +} diff --git a/src/main/java/io/github/onecx/theme/rs/internal/mappers/ExceptionMapper.java b/src/main/java/io/github/onecx/theme/rs/internal/mappers/ExceptionMapper.java index e60ce3c..404c42d 100644 --- a/src/main/java/io/github/onecx/theme/rs/internal/mappers/ExceptionMapper.java +++ b/src/main/java/io/github/onecx/theme/rs/internal/mappers/ExceptionMapper.java @@ -23,6 +23,7 @@ public abstract class ExceptionMapper { public RestResponse constraint(ConstraintViolationException ex) { + log.error("Processing theme internal rest controller error: {}", ex.getMessage()); var dto = exception("CONSTRAINT_VIOLATIONS", ex.getMessage()); dto.setValidations(createErrorValidationResponse(ex.getConstraintViolations())); @@ -30,9 +31,8 @@ public RestResponse constraint(ConstraintViolationException ex } public RestResponse exception(Exception ex) { - log.error("Processing portal internal rest controller error: {}", ex.getMessage()); + log.error("Processing theme internal rest controller error: {}", ex.getMessage()); - ex.printStackTrace(); if (ex instanceof DAOException de) { return RestResponse.status(Response.Status.BAD_REQUEST, exception(de.getMessageKey().name(), ex.getMessage(), de.parameters)); diff --git a/src/main/openapi/di-theme-v1.yaml b/src/main/openapi/di-theme-v1.yaml index 2be4080..79ca238 100644 --- a/src/main/openapi/di-theme-v1.yaml +++ b/src/main/openapi/di-theme-v1.yaml @@ -1,7 +1,7 @@ --- openapi: 3.0.3 info: - title: onecx-theme theme import + title: onecx-theme themes import from file during the start of the application version: 1.0.0 servers: - url: "http://localhost" @@ -23,20 +23,22 @@ components: type: object properties: themes: - type: array - items: - $ref: '#/components/schemas/DataImportTheme' - DataImportTheme: - required: - - name + $ref: '#/components/schemas/DataImportThemes' + DataImportThemes: type: object + nullable: false properties: - version: - format: int32 - type: integer - name: - minLength: 2 + default: type: string + minLength: 2 + description: name of the theme + required: + - default + additionalProperties: + $ref: '#/components/schemas/DataImportTheme' + DataImportTheme: + type: object + properties: cssFile: type: string description: diff --git a/src/main/openapi/onecx-theme-exim-v1-openapi.yaml b/src/main/openapi/onecx-theme-exim-v1-openapi.yaml new file mode 100644 index 0000000..0f961a2 --- /dev/null +++ b/src/main/openapi/onecx-theme-exim-v1-openapi.yaml @@ -0,0 +1,184 @@ +--- +openapi: 3.0.3 +info: + title: onecx-theme export and import v1 + version: 1.0.0 +servers: + - url: "http://onecx-portal:8080" +tags: + - name: themesExportImport +paths: + /exim/v1/themes/export: + post: + tags: + - themesExportImport + description: Export list of themes + operationId: exportThemes + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/EximExportRequest' + responses: + "200": + description: OK + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/EximImportRequest' + "404": + description: No themes founds + "500": + description: Internal Server Error + content: + application/json: + schema: + $ref: '#/components/schemas/EximRestException' + /exim/v1/themes/import: + post: + tags: + - themesExportImport + description: Import themes + operationId: importThemes + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/EximImportRequest' + responses: + "200": + description: Import result + content: + application/json: + schema: + $ref: '#/components/schemas/EximImportResult' + "400": + description: Bad request + content: + application/json: + schema: + $ref: '#/components/schemas/EximRestException' + "500": + description: Internal Server Error + content: + application/json: + schema: + $ref: '#/components/schemas/EximRestException' +components: + schemas: + EximExportRequest: + type: object + properties: + names: + type: array + uniqueItems: true + items: + type: string + EximImportResult: + type: object + properties: + id: + type: string + minLength: 10 + description: ID of the request + themes: + $ref: '#/components/schemas/EximImportResultThemes' + EximImportResultThemes: + type: object + nullable: false + properties: + default: + type: string + minLength: 2 + description: name of the theme + required: + - default + additionalProperties: + $ref: '#/components/schemas/EximThemeResult' + EximThemeResult: + type: object + properties: + status: + $ref: '#/components/schemas/EximThemeResultStatus' + EximThemeResultStatus: + type: string + enum: + - UPDATE + - CREATED + - SKIP + EximImportRequest: + type: object + properties: + id: + type: string + minLength: 10 + description: ID of the request + created: + $ref: '#/components/schemas/OffsetDateTime' + themes: + $ref: '#/components/schemas/EximImportThemes' + EximImportThemes: + type: object + nullable: false + properties: + default: + type: string + minLength: 2 + description: name of the theme + required: + - default + additionalProperties: + $ref: '#/components/schemas/EximTheme' + EximTheme: + type: object + properties: + cssFile: + type: string + description: + type: string + assetsUrl: + type: string + logoUrl: + type: string + faviconUrl: + type: string + previewImageUrl: + type: string + assetsUpdateDate: + type: string + properties: + type: object + EximRestException: + type: object + properties: + errorCode: + type: string + message: + type: string + parameters: + type: array + items: + type: object + namedParameters: + type: object + additionalProperties: + type: object + validations: + type: array + items: + $ref: '#/components/schemas/EximValidationConstraint' + EximValidationConstraint: + type: object + properties: + parameter: + type: string + message: + type: string + OffsetDateTime: + format: date-time + type: string + example: 2022-03-10T12:15:50-04:00 \ No newline at end of file diff --git a/src/test/java/io/github/onecx/theme/domain/daos/ThemeDAOTest.java b/src/test/java/io/github/onecx/theme/domain/daos/ThemeDAOTest.java index 108a271..9052a4d 100644 --- a/src/test/java/io/github/onecx/theme/domain/daos/ThemeDAOTest.java +++ b/src/test/java/io/github/onecx/theme/domain/daos/ThemeDAOTest.java @@ -30,6 +30,8 @@ void beforeAll() { void methodExceptionTests() { methodExceptionTests(() -> dao.findThemeByName(null), ThemeDAO.ErrorKeys.ERROR_FIND_THEME_BY_NAME); + methodExceptionTests(() -> dao.findThemeByNames(null), + ThemeDAO.ErrorKeys.ERROR_FIND_THEME_BY_NAMES); } void methodExceptionTests(Executable fn, Enum key) { diff --git a/src/test/java/io/github/onecx/theme/domain/di/ThemeDataImportServiceTest.java b/src/test/java/io/github/onecx/theme/domain/di/ThemeDataImportServiceTest.java index 2d78e2a..c100a8f 100644 --- a/src/test/java/io/github/onecx/theme/domain/di/ThemeDataImportServiceTest.java +++ b/src/test/java/io/github/onecx/theme/domain/di/ThemeDataImportServiceTest.java @@ -16,6 +16,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import gen.io.github.onecx.theme.di.v1.model.DataImportDTOV1; +import gen.io.github.onecx.theme.di.v1.model.DataImportThemesDTOV1; import io.github.onecx.theme.domain.daos.ThemeDAO; import io.github.onecx.theme.domain.models.Theme; import io.github.onecx.theme.test.AbstractTest; @@ -101,7 +102,7 @@ public Map getMetadata() { public byte[] getData() { try { var data = new DataImportDTOV1(); - data.setThemes(List.of()); + data.setThemes(new DataImportThemesDTOV1()); return mapper.writeValueAsBytes(data); } catch (Exception ex) { throw new RuntimeException(ex); diff --git a/src/test/java/io/github/onecx/theme/rs/exim/v1/controllers/ExportImportRestControllerV1ExceptionTest.java b/src/test/java/io/github/onecx/theme/rs/exim/v1/controllers/ExportImportRestControllerV1ExceptionTest.java new file mode 100644 index 0000000..84aa432 --- /dev/null +++ b/src/test/java/io/github/onecx/theme/rs/exim/v1/controllers/ExportImportRestControllerV1ExceptionTest.java @@ -0,0 +1,67 @@ +package io.github.onecx.theme.rs.exim.v1.controllers; + +import static io.restassured.RestAssured.given; +import static jakarta.ws.rs.core.MediaType.APPLICATION_JSON; +import static jakarta.ws.rs.core.Response.Status.*; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.notNull; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import org.tkit.quarkus.jpa.exceptions.DAOException; + +import gen.io.github.onecx.theme.rs.exim.v1.model.EximExportRequestDTOV1; +import gen.io.github.onecx.theme.rs.exim.v1.model.EximRestExceptionDTOV1; +import io.github.onecx.theme.domain.daos.ThemeDAO; +import io.github.onecx.theme.test.AbstractTest; +import io.quarkus.test.InjectMock; +import io.quarkus.test.common.http.TestHTTPEndpoint; +import io.quarkus.test.junit.QuarkusTest; + +@QuarkusTest +@TestHTTPEndpoint(ExportImportRestControllerV1.class) +class ExportImportRestControllerV1ExceptionTest extends AbstractTest { + + @InjectMock + ThemeDAO dao; + + @BeforeEach + void beforeAll() { + Mockito.when(dao.findThemeByNames(notNull())) + .thenThrow(new RuntimeException("Test technical error exception")) + .thenThrow(new DAOException(ErrorKey.ERROR_TEST, new RuntimeException("Test"))); + } + + @Test + void exportThemesExceptionTest() { + + var request = new EximExportRequestDTOV1(); + + var exception = given() + .when() + .contentType(APPLICATION_JSON) + .body(request) + .post("export") + .then() + .statusCode(INTERNAL_SERVER_ERROR.getStatusCode()) + .extract().as(EximRestExceptionDTOV1.class); + + assertThat(exception.getErrorCode()).isEqualTo("UNDEFINED_ERROR_CODE"); + + exception = given() + .when() + .contentType(APPLICATION_JSON) + .body(request) + .post("export") + .then().log().all() + .statusCode(BAD_REQUEST.getStatusCode()) + .extract().as(EximRestExceptionDTOV1.class); + + assertThat(exception.getErrorCode()).isEqualTo(ErrorKey.ERROR_TEST.name()); + } + + public enum ErrorKey { + ERROR_TEST; + } +} diff --git a/src/test/java/io/github/onecx/theme/rs/exim/v1/controllers/ExportImportRestControllerV1IT.java b/src/test/java/io/github/onecx/theme/rs/exim/v1/controllers/ExportImportRestControllerV1IT.java new file mode 100644 index 0000000..9da0863 --- /dev/null +++ b/src/test/java/io/github/onecx/theme/rs/exim/v1/controllers/ExportImportRestControllerV1IT.java @@ -0,0 +1,7 @@ +package io.github.onecx.theme.rs.exim.v1.controllers; + +import io.quarkus.test.junit.QuarkusIntegrationTest; + +@QuarkusIntegrationTest +public class ExportImportRestControllerV1IT extends ExportImportRestControllerV1Test { +} diff --git a/src/test/java/io/github/onecx/theme/rs/exim/v1/controllers/ExportImportRestControllerV1Test.java b/src/test/java/io/github/onecx/theme/rs/exim/v1/controllers/ExportImportRestControllerV1Test.java new file mode 100644 index 0000000..d18dc32 --- /dev/null +++ b/src/test/java/io/github/onecx/theme/rs/exim/v1/controllers/ExportImportRestControllerV1Test.java @@ -0,0 +1,101 @@ +package io.github.onecx.theme.rs.exim.v1.controllers; + +import static io.restassured.RestAssured.given; +import static jakarta.ws.rs.core.MediaType.APPLICATION_JSON; +import static jakarta.ws.rs.core.Response.Status.*; +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.HashSet; +import java.util.Set; + +import org.junit.jupiter.api.Test; +import org.tkit.quarkus.test.WithDBData; + +import gen.io.github.onecx.theme.rs.exim.v1.model.EximExportRequestDTOV1; +import gen.io.github.onecx.theme.rs.exim.v1.model.EximImportRequestDTOV1; +import gen.io.github.onecx.theme.rs.exim.v1.model.EximRestExceptionDTOV1; +import io.github.onecx.theme.test.AbstractTest; +import io.quarkus.test.common.http.TestHTTPEndpoint; +import io.quarkus.test.junit.QuarkusTest; + +@QuarkusTest +@TestHTTPEndpoint(ExportImportRestControllerV1.class) +@WithDBData(value = "data/testdata-exim.xml", deleteBeforeInsert = true, deleteAfterTest = true, rinseAndRepeat = true) +class ExportImportRestControllerV1Test extends AbstractTest { + + @Test + void exportThemesTest() { + + var request = new EximExportRequestDTOV1(); + + var dto = given() + .when() + .contentType(APPLICATION_JSON) + .body(request) + .post("export") + .then() + .statusCode(OK.getStatusCode()) + .extract().as(EximImportRequestDTOV1.class); + assertThat(dto).isNotNull(); + assertThat(dto.getThemes()).hasSize(3); + + request.setNames(new HashSet<>()); + dto = given() + .when() + .contentType(APPLICATION_JSON) + .body(request) + .post("export") + .then() + .statusCode(OK.getStatusCode()) + .extract().as(EximImportRequestDTOV1.class); + assertThat(dto).isNotNull(); + assertThat(dto.getThemes()).hasSize(3); + + request.setNames(Set.of("cg", "themeWithoutPortal")); + dto = given() + .when() + .contentType(APPLICATION_JSON) + .body(request) + .post("export") + .then() + .statusCode(OK.getStatusCode()) + .extract().as(EximImportRequestDTOV1.class); + assertThat(dto).isNotNull(); + assertThat(dto.getThemes()).hasSize(2); + } + + @Test + void exportThemesWrongNamesTest() { + + var request = new EximExportRequestDTOV1(); + request.setNames(Set.of("does-not-exists")); + + given() + .when() + .contentType(APPLICATION_JSON) + .body(request) + .post("export") + .then() + .statusCode(NOT_FOUND.getStatusCode()); + } + + @Test + void exportThemesEmptyBodyTest() { + + var exception = given() + .when() + .contentType(APPLICATION_JSON) + .post("export") + .then() + .statusCode(BAD_REQUEST.getStatusCode()) + .extract().as(EximRestExceptionDTOV1.class); + + assertThat(exception.getErrorCode()).isEqualTo("CONSTRAINT_VIOLATIONS"); + assertThat(exception.getMessage()).isEqualTo("exportThemes.eximExportRequestDTOV1: must not be null"); + } + + @Test + void importThemesTest() { + + } +} diff --git a/src/test/java/io/github/onecx/theme/rs/internal/controllers/ThemesRestControllerTest.java b/src/test/java/io/github/onecx/theme/rs/internal/controllers/ThemesRestControllerTest.java index 7524efa..84df1c0 100644 --- a/src/test/java/io/github/onecx/theme/rs/internal/controllers/ThemesRestControllerTest.java +++ b/src/test/java/io/github/onecx/theme/rs/internal/controllers/ThemesRestControllerTest.java @@ -4,6 +4,7 @@ import static jakarta.ws.rs.core.MediaType.APPLICATION_JSON; import static jakarta.ws.rs.core.Response.Status.*; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.from; import java.util.List; @@ -54,12 +55,11 @@ void createNewThemeTest() { .extract() .body().as(ThemeDTO.class); - assertThat(dto).isNotNull(); - assertThat(dto.getName()).isEqualTo(themeDto.getName()); - assertThat(dto.getName()).isEqualTo(themeDto.getName()); - assertThat(dto.getDescription()).isEqualTo(themeDto.getDescription()); - assertThat(dto.getAssetsUrl()).isEqualTo(themeDto.getAssetsUrl()); - assertThat(dto.getPreviewImageUrl()).isEqualTo(themeDto.getPreviewImageUrl()); + assertThat(dto).isNotNull() + .returns(themeDto.getName(), from(ThemeDTO::getName)) + .returns(themeDto.getDescription(), from(ThemeDTO::getDescription)) + .returns(themeDto.getAssetsUrl(), from(ThemeDTO::getAssetsUrl)) + .returns(themeDto.getPreviewImageUrl(), from(ThemeDTO::getPreviewImageUrl)); // create theme without body var exception = given() @@ -81,7 +81,7 @@ void createNewThemeTest() { .contentType(APPLICATION_JSON) .body(themeDto) .post() - .then().log().all() + .then() .statusCode(BAD_REQUEST.getStatusCode()) .extract().as(RestExceptionDTO.class); @@ -244,7 +244,7 @@ void updateThemeWithExistingNameTest() { .body(themeDto) .pathParam("id", "11-111") .put("{id}") - .then().log().all() + .then() .statusCode(BAD_REQUEST.getStatusCode()) .extract().as(RestExceptionDTO.class); @@ -264,7 +264,7 @@ void updateThemeWithoutBodyTest() { .when() .pathParam("id", "update_create_new") .put("{id}") - .then().log().all() + .then() .statusCode(BAD_REQUEST.getStatusCode()) .extract().as(RestExceptionDTO.class); diff --git a/src/test/resources/data/testdata-exim.xml b/src/test/resources/data/testdata-exim.xml new file mode 100644 index 0000000..47813f2 --- /dev/null +++ b/src/test/resources/data/testdata-exim.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/test/resources/import/theme-import.json b/src/test/resources/import/theme-import.json index 64d9e7f..3189b41 100644 --- a/src/test/resources/import/theme-import.json +++ b/src/test/resources/import/theme-import.json @@ -1,9 +1,6 @@ { - "themes": [ - { - "id": "T_0", - "themeId": "Theme1", - "name": "Theme1", + "themes": { + "Theme1": { "description": "Theme1 basic color scheme", "properties": { "topbar": { @@ -42,11 +39,8 @@ } } }, - { - "id": "T_2", + "Theme2": { "description": "Theme2 theme", - "themeId": "Theme2", - "name": "Theme2", "properties": { "topbar": { "topbar-bg-color": "#e20074", @@ -85,5 +79,5 @@ } } } - ] + } }