diff --git a/src/main/java/org/fontory/fontorybe/config/OctetStreamReadMessageConverter.java b/src/main/java/org/fontory/fontorybe/config/OctetStreamReadMessageConverter.java new file mode 100644 index 0000000..ea9b73a --- /dev/null +++ b/src/main/java/org/fontory/fontorybe/config/OctetStreamReadMessageConverter.java @@ -0,0 +1,33 @@ +package org.fontory.fontorybe.config; + +import com.fasterxml.jackson.databind.ObjectMapper; +import java.lang.reflect.Type; +import org.springframework.http.MediaType; +import org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter; +import org.springframework.stereotype.Component; + +/** + * For Swagger to use RequestPart and dto + */ +@Component +public class OctetStreamReadMessageConverter extends AbstractJackson2HttpMessageConverter { + + public OctetStreamReadMessageConverter(ObjectMapper objectMapper) { + super(objectMapper, MediaType.APPLICATION_OCTET_STREAM); + } + + @Override + public boolean canWrite(Class clazz, MediaType mediaType) { + return false; + } + + @Override + public boolean canWrite(Type type, Class clazz, MediaType mediaType) { + return false; + } + + @Override + protected boolean canWrite(MediaType mediaType) { + return false; + } +} \ No newline at end of file diff --git a/src/main/java/org/fontory/fontorybe/file/adapter/inbound/FileRequestMapper.java b/src/main/java/org/fontory/fontorybe/file/adapter/inbound/FileRequestMapper.java index 84a4f1c..f265833 100644 --- a/src/main/java/org/fontory/fontorybe/file/adapter/inbound/FileRequestMapper.java +++ b/src/main/java/org/fontory/fontorybe/file/adapter/inbound/FileRequestMapper.java @@ -45,6 +45,20 @@ public FileCreate toProfileImageFileCreate(MultipartFile file, Long memberId) { .build(); } + public FileCreate toFontTemplateImageFileCreate(MultipartFile file, Long memberId) { + Member foundMember = memberService.getOrThrowById(memberId); + Provide provide = provideService.getOrThrownById(foundMember.getProvideId()); + + validateImageFile(file); + String generatedFileName = generateFileName(file, provide); + + return FileCreate.builder() + .file(file) + .fileType(FileType.FONT_PAPER) + .fileName(generatedFileName) + .build(); + } + private void validateImageFile(MultipartFile file) { if (file == null || file.isEmpty()) { throw new FileEmptyException("File is empty."); diff --git a/src/main/java/org/fontory/fontorybe/file/adapter/outboud/s3/AmazonS3BucketService.java b/src/main/java/org/fontory/fontorybe/file/adapter/outboud/s3/AmazonS3BucketService.java index db3c3ee..43f92c2 100644 --- a/src/main/java/org/fontory/fontorybe/file/adapter/outboud/s3/AmazonS3BucketService.java +++ b/src/main/java/org/fontory/fontorybe/file/adapter/outboud/s3/AmazonS3BucketService.java @@ -50,4 +50,22 @@ private void deleteLastImages(String fileName) { s3Template.deleteObject(profileImageBucketName,fileNameWithoutExtension + ".jpeg"); s3Template.deleteObject(profileImageBucketName,fileNameWithoutExtension + ".png"); } + + @Override + public FileMetadata uploadFontTemplateImage(FileCreate request) { + AmazonS3PutRequest amazonS3PutRequest = AmazonS3PutRequest.from(request, clockHolder.getCurrentTimeStamp()); + + try (InputStream inputStream = amazonS3PutRequest.getFile().getInputStream()) { + S3Resource s3Resource = s3Template.upload( + fontPaperBucketName, + amazonS3PutRequest.getKey(), + inputStream); + + String objectUrl = s3Resource.getURL().toExternalForm(); + + return AmazonS3ObjectMetadata.from(amazonS3PutRequest, objectUrl).toModel(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } } diff --git a/src/main/java/org/fontory/fontorybe/file/adapter/outboud/s3/CloudStorageService.java b/src/main/java/org/fontory/fontorybe/file/adapter/outboud/s3/CloudStorageService.java index 7da8266..05ad7ff 100644 --- a/src/main/java/org/fontory/fontorybe/file/adapter/outboud/s3/CloudStorageService.java +++ b/src/main/java/org/fontory/fontorybe/file/adapter/outboud/s3/CloudStorageService.java @@ -6,4 +6,6 @@ public interface CloudStorageService { FileMetadata uploadProfileImage(FileCreate fileCreate); + + FileMetadata uploadFontTemplateImage(FileCreate fileCreate); } diff --git a/src/main/java/org/fontory/fontorybe/file/application/FileService.java b/src/main/java/org/fontory/fontorybe/file/application/FileService.java index 8c796ce..6f2daf2 100644 --- a/src/main/java/org/fontory/fontorybe/file/application/FileService.java +++ b/src/main/java/org/fontory/fontorybe/file/application/FileService.java @@ -6,4 +6,6 @@ public interface FileService { FileDetails uploadProfileImage(FileCreate fileCreate); + + FileDetails uploadFontTemplateImage(FileCreate fileCreate); } diff --git a/src/main/java/org/fontory/fontorybe/file/application/FileServiceImpl.java b/src/main/java/org/fontory/fontorybe/file/application/FileServiceImpl.java index 934dccc..47983c7 100644 --- a/src/main/java/org/fontory/fontorybe/file/application/FileServiceImpl.java +++ b/src/main/java/org/fontory/fontorybe/file/application/FileServiceImpl.java @@ -19,4 +19,11 @@ public FileDetails uploadProfileImage(FileCreate fileCreate) { return FileDetails.from(fileMetadata); } + + @Override + public FileDetails uploadFontTemplateImage(FileCreate fileCreate) { + FileMetadata fileMetadata = cloudStorageService.uploadFontTemplateImage(fileCreate); + + return FileDetails.from(fileMetadata); + } } diff --git a/src/main/java/org/fontory/fontorybe/font/controller/FontController.java b/src/main/java/org/fontory/fontorybe/font/controller/FontController.java index f73f65c..b74e434 100644 --- a/src/main/java/org/fontory/fontorybe/font/controller/FontController.java +++ b/src/main/java/org/fontory/fontorybe/font/controller/FontController.java @@ -1,9 +1,25 @@ package org.fontory.fontorybe.font.controller; -import java.util.List; +import static org.fontory.fontorybe.file.validator.MultipartFileValidator.extractSingleMultipartFile; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.ArraySchema; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.tags.Tag; +import java.util.List; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.fontory.fontorybe.authentication.adapter.inbound.Login; import org.fontory.fontorybe.authentication.domain.UserPrincipal; +import org.fontory.fontorybe.file.adapter.inbound.FileRequestMapper; +import org.fontory.fontorybe.file.adapter.inbound.dto.FileUploadResponse; +import org.fontory.fontorybe.file.application.FileService; +import org.fontory.fontorybe.file.domain.FileCreate; +import org.fontory.fontorybe.file.domain.FileDetails; import org.fontory.fontorybe.font.controller.dto.FontCreateDTO; import org.fontory.fontorybe.font.controller.dto.FontCreateResponse; import org.fontory.fontorybe.font.controller.dto.FontDeleteResponse; @@ -18,9 +34,11 @@ import org.fontory.fontorybe.font.domain.Font; import org.springframework.data.domain.Page; import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PatchMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; @@ -28,16 +46,9 @@ import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RequestPart; import org.springframework.web.bind.annotation.RestController; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; - -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.Parameter; -import io.swagger.v3.oas.annotations.tags.Tag; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; +import org.springframework.web.multipart.MultipartFile; @Slf4j @Tag(name = "폰트 관리", description = "폰트 API") @@ -46,7 +57,9 @@ @RequiredArgsConstructor public class FontController { private final FontService fontService; + private final FileService fileService; private final ObjectMapper objectMapper; + private final FileRequestMapper fileRequestMapper; /** * Convert an object to JSON string for logging @@ -62,19 +75,53 @@ private String toJson(Object obj) { } @Operation(summary = "폰트 생성") - @PostMapping - public ResponseEntity addFont(@RequestBody FontCreateDTO fontCreateDTO, @Login UserPrincipal userPrincipal) { + @PostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE) + public ResponseEntity addFont( + @Login UserPrincipal userPrincipal, + @RequestPart("fontCreateDTO") FontCreateDTO fontCreateDTO, + @Parameter( + description = "업로드할 파일. 정확히 1개의 파일만 제공되어야 합니다.", + required = true, + content = @Content( + mediaType = MediaType.MULTIPART_FORM_DATA_VALUE, + array = @ArraySchema( + schema = @Schema(type = "string", format = "binary"), + maxItems = 1 + ) + ) + ) + @RequestPart("file") List files + ) { Long memberId = userPrincipal.getId(); - log.info("Request received: Create font for member ID: {}, request: {}", + + MultipartFile file = extractSingleMultipartFile(files); + + log.info("Request received: Create font and Upload font template image for member ID: {}, request: {}", memberId, toJson(fontCreateDTO)); - Font createdFont = fontService.create(memberId, fontCreateDTO); - log.info("Response sent: Font created with ID: {}, name: {}", - createdFont.getId(), createdFont.getName()); + logFileDetails(file, "Font template image upload"); + + FileCreate fileCreate = fileRequestMapper.toFontTemplateImageFileCreate(file, memberId); + FileDetails fileDetails = fileService.uploadFontTemplateImage(fileCreate); + FileUploadResponse fileUploadResponse = FileUploadResponse.from(fileDetails); + + Font createdFont = fontService.create(memberId, fontCreateDTO, fileDetails); + + log.info("Response sent: Font created with ID: {}, name: {} and Font template image uploaded successfully, url: {}, fileName: {}, size: {} bytes", + createdFont.getId(), createdFont.getName(), fileDetails.getFileUrl(), fileDetails.getFileName(), fileDetails.getSize()); return ResponseEntity .status(HttpStatus.CREATED) - .body(FontCreateResponse.from(createdFont)); + .body(FontCreateResponse.from(createdFont, fileUploadResponse)); + } + + private void logFileDetails(MultipartFile file, String context) { + log.debug("{} - File details: name='{}', original name='{}', size={} bytes, contentType='{}'", + context, + file.getName(), + file.getOriginalFilename(), + file.getSize(), + file.getContentType()); } @Operation(summary = "폰트 제작 상황") diff --git a/src/main/java/org/fontory/fontorybe/font/controller/dto/FontCreateResponse.java b/src/main/java/org/fontory/fontorybe/font/controller/dto/FontCreateResponse.java index a845377..700a9ab 100644 --- a/src/main/java/org/fontory/fontorybe/font/controller/dto/FontCreateResponse.java +++ b/src/main/java/org/fontory/fontorybe/font/controller/dto/FontCreateResponse.java @@ -3,6 +3,7 @@ import java.time.LocalDateTime; import lombok.Builder; import lombok.Getter; +import org.fontory.fontorybe.file.adapter.inbound.dto.FileUploadResponse; import org.fontory.fontorybe.font.domain.Font; import org.fontory.fontorybe.font.infrastructure.entity.FontStatus; @@ -14,14 +15,16 @@ public class FontCreateResponse { private FontStatus status; private Long memberId; private LocalDateTime createdAt; + private FileUploadResponse fileUploadResponse; - public static FontCreateResponse from(Font font) { + public static FontCreateResponse from(Font font, FileUploadResponse fileUploadResponse) { return FontCreateResponse.builder() .id(font.getId()) .name(font.getName()) .status(font.getStatus()) .memberId(font.getMemberId()) .createdAt(font.getCreatedAt()) + .fileUploadResponse(fileUploadResponse) .build(); } } diff --git a/src/main/java/org/fontory/fontorybe/font/controller/port/FontService.java b/src/main/java/org/fontory/fontorybe/font/controller/port/FontService.java index 94b90bd..00fca17 100644 --- a/src/main/java/org/fontory/fontorybe/font/controller/port/FontService.java +++ b/src/main/java/org/fontory/fontorybe/font/controller/port/FontService.java @@ -1,6 +1,7 @@ package org.fontory.fontorybe.font.controller.port; import java.util.List; +import org.fontory.fontorybe.file.domain.FileDetails; import org.fontory.fontorybe.font.controller.dto.FontCreateDTO; import org.fontory.fontorybe.font.controller.dto.FontDeleteResponse; import org.fontory.fontorybe.font.controller.dto.FontDetailResponse; @@ -13,7 +14,7 @@ import org.springframework.data.domain.Page; public interface FontService { - Font create(Long memberId, FontCreateDTO fontCreateDTO); + Font create(Long memberId, FontCreateDTO fontCreateDTO, FileDetails fileDetails); List getFontProgress(Long memberId); Font update(Long memberId, Long fontId, FontUpdateDTO fontUpdateDTO); Font getOrThrowById(Long id); diff --git a/src/main/java/org/fontory/fontorybe/font/domain/Font.java b/src/main/java/org/fontory/fontorybe/font/domain/Font.java index 0953b43..6c9d597 100644 --- a/src/main/java/org/fontory/fontorybe/font/domain/Font.java +++ b/src/main/java/org/fontory/fontorybe/font/domain/Font.java @@ -6,6 +6,7 @@ import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; +import org.fontory.fontorybe.file.domain.FileDetails; import org.fontory.fontorybe.font.controller.dto.FontCreateDTO; import org.fontory.fontorybe.font.controller.dto.FontProgressUpdateDTO; import org.fontory.fontorybe.font.controller.dto.FontUpdateDTO; @@ -35,6 +36,8 @@ public class Font { private Long memberId; + private String templateURL; + private LocalDateTime createdAt; private LocalDateTime updatedAt; @@ -51,7 +54,7 @@ public void increaseDownloadCount() { this.downloadCount++; } - public static Font from(FontCreateDTO fontCreateDTO, Long memberId) { + public static Font from(FontCreateDTO fontCreateDTO, Long memberId, FileDetails fileDetails) { return Font.builder() .name(fontCreateDTO.getName()) .status(FontStatus.PROGRESS) @@ -59,6 +62,7 @@ public static Font from(FontCreateDTO fontCreateDTO, Long memberId) { .downloadCount(0L) .bookmarkCount(0L) .memberId(memberId) + .templateURL(fileDetails.getFileUrl()) .build(); } @@ -73,6 +77,7 @@ public Font update(FontUpdateDTO fontUpdateDTO) { .ttf(this.ttf) .woff(this.woff) .memberId(this.memberId) + .templateURL(this.templateURL) .createdAt(this.createdAt) .updatedAt(this.updatedAt) .build(); @@ -89,6 +94,7 @@ public Font updateProgress(FontProgressUpdateDTO fontProgressUpdateDTO) { .ttf(this.ttf) .woff(this.woff) .memberId(this.memberId) + .templateURL(this.templateURL) .createdAt(this.createdAt) .updatedAt(this.updatedAt) .build(); diff --git a/src/main/java/org/fontory/fontorybe/font/infrastructure/entity/FontEntity.java b/src/main/java/org/fontory/fontorybe/font/infrastructure/entity/FontEntity.java index c535589..4c7f250 100644 --- a/src/main/java/org/fontory/fontorybe/font/infrastructure/entity/FontEntity.java +++ b/src/main/java/org/fontory/fontorybe/font/infrastructure/entity/FontEntity.java @@ -46,6 +46,8 @@ public class FontEntity extends BaseEntity { private Long memberId; + private String templateURL; + public Font toModel() { return Font.builder() .id(id) @@ -57,6 +59,7 @@ public Font toModel() { .ttf(ttf) .woff(woff) .memberId(memberId) + .templateURL(templateURL) .createdAt(getCreatedAt()) .updatedAt(getUpdatedAt()) .build(); @@ -73,6 +76,7 @@ public static FontEntity from(Font font) { .ttf(font.getTtf()) .woff(font.getWoff()) .memberId(font.getMemberId()) + .templateURL(font.getTemplateURL()) .createdAt(font.getCreatedAt()) .updatedAt(font.getUpdatedAt()) .build(); diff --git a/src/main/java/org/fontory/fontorybe/font/service/FontServiceImpl.java b/src/main/java/org/fontory/fontorybe/font/service/FontServiceImpl.java index d07dbac..a6a5b84 100644 --- a/src/main/java/org/fontory/fontorybe/font/service/FontServiceImpl.java +++ b/src/main/java/org/fontory/fontorybe/font/service/FontServiceImpl.java @@ -2,8 +2,10 @@ import java.util.List; import java.util.stream.Collectors; - +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.fontory.fontorybe.bookmark.service.port.BookmarkRepository; +import org.fontory.fontorybe.file.domain.FileDetails; import org.fontory.fontorybe.font.controller.dto.FontCreateDTO; import org.fontory.fontorybe.font.controller.dto.FontDeleteResponse; import org.fontory.fontorybe.font.controller.dto.FontDetailResponse; @@ -16,7 +18,9 @@ import org.fontory.fontorybe.font.domain.Font; import org.fontory.fontorybe.font.domain.exception.FontNotFoundException; import org.fontory.fontorybe.font.domain.exception.FontOwnerMismatchException; +import org.fontory.fontorybe.font.service.dto.FontRequestProduceDto; import org.fontory.fontorybe.font.service.port.FontRepository; +import org.fontory.fontorybe.font.service.port.FontRequestProducer; import org.fontory.fontorybe.member.controller.port.MemberService; import org.fontory.fontorybe.member.domain.Member; import org.springframework.data.domain.Page; @@ -26,25 +30,27 @@ import org.springframework.transaction.annotation.Transactional; import org.springframework.util.StringUtils; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; - @Slf4j @Service @RequiredArgsConstructor public class FontServiceImpl implements FontService { private final FontRepository fontRepository; private final BookmarkRepository bookmarkRepository; + private final MemberService memberService; + private final FontRequestProducer fontRequestProducer; + @Override @Transactional - public Font create(Long memberId, FontCreateDTO fontCreateDTO) { + public Font create(Long memberId, FontCreateDTO fontCreateDTO, FileDetails fileDetails) { log.info("Service executing: Creating font for member ID: {}, font name: {}", memberId, fontCreateDTO.getName()); Member member = memberService.getOrThrowById(memberId); - Font savedFont = fontRepository.save(Font.from(fontCreateDTO, member.getId())); - log.info("Service completed: Font created with ID: {}", savedFont.getId()); + Font savedFont = fontRepository.save(Font.from(fontCreateDTO, member.getId(), fileDetails)); + fontRequestProducer.sendFontRequest(FontRequestProduceDto.from(savedFont, member)); + + log.info("Service completed: Font created with ID: {} and Font template image uploaded successfully", savedFont.getId()); return savedFont; } diff --git a/src/main/java/org/fontory/fontorybe/font/service/dto/FontRequestProduceDto.java b/src/main/java/org/fontory/fontorybe/font/service/dto/FontRequestProduceDto.java index fb1698c..24e8bec 100644 --- a/src/main/java/org/fontory/fontorybe/font/service/dto/FontRequestProduceDto.java +++ b/src/main/java/org/fontory/fontorybe/font/service/dto/FontRequestProduceDto.java @@ -1,8 +1,12 @@ package org.fontory.fontorybe.font.service.dto; +import java.util.UUID; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; +import org.fontory.fontorybe.font.domain.Font; +import org.fontory.fontorybe.member.domain.Member; +import org.slf4j.MDC; @Getter @Builder @@ -14,4 +18,15 @@ public class FontRequestProduceDto { private String templateURL; private String author; private String requestUUID; + + public static FontRequestProduceDto from(Font font, Member member) { + return FontRequestProduceDto.builder() + .memberId(member.getId()) + .fontId(font.getId()) + .fontName(font.getName()) + .templateURL(font.getTemplateURL()) + .author(member.getNickname()) + .requestUUID(MDC.get("requestId")) + .build(); + } } diff --git a/src/test/java/org/fontory/fontorybe/integration/font/FontControllerIntegrationTest.java b/src/test/java/org/fontory/fontorybe/integration/font/FontControllerIntegrationTest.java index bee50e1..8dd493c 100644 --- a/src/test/java/org/fontory/fontorybe/integration/font/FontControllerIntegrationTest.java +++ b/src/test/java/org/fontory/fontorybe/integration/font/FontControllerIntegrationTest.java @@ -3,8 +3,12 @@ import static org.hamcrest.Matchers.greaterThanOrEqualTo; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.lessThanOrEqualTo; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.doNothing; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.multipart; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; @@ -13,13 +17,19 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import com.fasterxml.jackson.databind.ObjectMapper; +import java.nio.charset.StandardCharsets; import org.fontory.fontorybe.authentication.adapter.outbound.JwtTokenProvider; import org.fontory.fontorybe.authentication.domain.UserPrincipal; import org.fontory.fontorybe.common.DevTokenInitializer; +import org.fontory.fontorybe.file.application.FileService; +import org.fontory.fontorybe.file.domain.FileCreate; +import org.fontory.fontorybe.file.domain.FileDetails; +import org.fontory.fontorybe.file.domain.FileType; import org.fontory.fontorybe.font.controller.dto.FontCreateDTO; import org.fontory.fontorybe.font.controller.dto.FontProgressUpdateDTO; import org.fontory.fontorybe.font.controller.dto.FontUpdateDTO; import org.fontory.fontorybe.font.infrastructure.entity.FontStatus; +import org.fontory.fontorybe.font.service.port.FontRequestProducer; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -27,6 +37,8 @@ import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.http.MediaType; +import org.springframework.mock.web.MockMultipartFile; +import org.springframework.test.context.bean.override.mockito.MockitoBean; import org.springframework.test.context.jdbc.Sql; import org.springframework.test.context.jdbc.Sql.ExecutionPhase; import org.springframework.test.web.servlet.MockMvc; @@ -46,6 +58,11 @@ class FontControllerIntegrationTest { @Autowired private DevTokenInitializer devTokenInitializer; + @MockitoBean + private FileService fileService; + @MockitoBean + private FontRequestProducer fontRequestProducer; + private final Long existMemberId = 999L; private final String existMemberName = "existMemberNickName"; @@ -67,11 +84,23 @@ class FontControllerIntegrationTest { private String validAccessToken; private String validFontCreateServerToken; + private FileDetails fileDetails; + @BeforeEach void setUp() { UserPrincipal userPrincipal = new UserPrincipal(existMemberId); validAccessToken = "Bearer " + jwtTokenProvider.generateAccessToken(userPrincipal); validFontCreateServerToken = "Bearer " + devTokenInitializer.getFixedTokenForFontCreateServer(); + + fileDetails = FileDetails.builder() + .fileName("fontTemplateImage.jpg") + .fileUrl("https://mock-s3.com/fake.jpg") + .size(1024L) + .build(); + + given(fileService.uploadFontTemplateImage(any())).willReturn(fileDetails); + + doNothing().when(fontRequestProducer).sendFontRequest(any()); } @Test @@ -85,10 +114,26 @@ void addFontSuccess() throws Exception { String jsonRequest = objectMapper.writeValueAsString(createDTO); + MockMultipartFile jsonPart = new MockMultipartFile( + "fontCreateDTO", + null, + "application/json", + jsonRequest.getBytes(StandardCharsets.UTF_8) + ); + + MockMultipartFile filePart = new MockMultipartFile( + "file", + "fontTemplateImage.jpg", + "image/jpeg", + "<<임시파일바이트>>".getBytes(StandardCharsets.UTF_8) + ); + // when & then - mockMvc.perform(post("/fonts") + mockMvc.perform(multipart("/fonts") + .file(jsonPart) + .file(filePart) .header("Authorization", validAccessToken) - .contentType(MediaType.APPLICATION_JSON) + .contentType(MediaType.MULTIPART_FORM_DATA) .content(jsonRequest)) .andExpect(status().isCreated()) .andExpect(jsonPath("$.name", is(newFontName))) diff --git a/src/test/java/org/fontory/fontorybe/integration/font/FontServiceIntegrationTest.java b/src/test/java/org/fontory/fontorybe/integration/font/FontServiceIntegrationTest.java index db783f2..2f682b3 100644 --- a/src/test/java/org/fontory/fontorybe/integration/font/FontServiceIntegrationTest.java +++ b/src/test/java/org/fontory/fontorybe/integration/font/FontServiceIntegrationTest.java @@ -3,10 +3,18 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.Assertions.assertAll; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.doNothing; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Comparator; import java.util.List; +import org.fontory.fontorybe.file.application.FileService; +import org.fontory.fontorybe.file.domain.FileCreate; +import org.fontory.fontorybe.file.domain.FileDetails; +import org.fontory.fontorybe.file.domain.FileType; import org.fontory.fontorybe.font.controller.dto.FontCreateDTO; import org.fontory.fontorybe.font.controller.dto.FontDeleteResponse; import org.fontory.fontorybe.font.controller.dto.FontDetailResponse; @@ -20,11 +28,15 @@ import org.fontory.fontorybe.font.domain.exception.FontNotFoundException; import org.fontory.fontorybe.font.domain.exception.FontOwnerMismatchException; import org.fontory.fontorybe.font.infrastructure.entity.FontStatus; +import org.fontory.fontorybe.font.service.port.FontRequestProducer; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.data.domain.Page; +import org.springframework.mock.web.MockMultipartFile; +import org.springframework.test.context.bean.override.mockito.MockitoBean; import org.springframework.test.context.jdbc.Sql; import org.springframework.test.context.jdbc.Sql.ExecutionPhase; @@ -34,6 +46,10 @@ class FontServiceIntegrationTest { @Autowired private FontService fontService; + @MockitoBean + private FileService fileService; + @MockitoBean + private FontRequestProducer fontRequestProducer; private final Long existMemberId = 999L; private final String existMemberName = "existMemberNickName"; @@ -51,6 +67,21 @@ class FontServiceIntegrationTest { private final String existFontTtf = "ttf주소"; private final String existFontWoff = "woff주소"; + private FileDetails fileDetails; + + @BeforeEach + void setup() { + fileDetails = FileDetails.builder() + .fileName("fontTemplateImage.jpg") + .fileUrl("https://mock-s3.com/fake.jpg") + .size(1024L) + .build(); + + given(fileService.uploadFontTemplateImage(any())).willReturn(fileDetails); + + doNothing().when(fontRequestProducer).sendFontRequest(any()); + } + @Test @DisplayName("font - create success test") void createFontSuccess() { @@ -61,7 +92,7 @@ void createFontSuccess() { .build(); // when - Font createdFont = fontService.create(existMemberId, dto); + Font createdFont = fontService.create(existMemberId, dto, fileDetails); // then assertAll( @@ -83,10 +114,14 @@ void createFontSuccess() { @DisplayName("font - getFontProgress success test") void getFontProgressSuccess() { for (int i = 1; i <= 6; i++) { - fontService.create(existMemberId, FontCreateDTO.builder() - .name("진행중폰트" + i) - .example("예제" + i) - .build()); + fontService.create( + existMemberId, + FontCreateDTO.builder() + .name("진행중폰트" + i) + .example("예제" + i) + .build(), + fileDetails + ); } // when @@ -128,7 +163,7 @@ void updateFontAccessDeniedFail() { .example("다른예제") .build(); - Font elseFont = fontService.create(createdMemberId, createDTO); + Font elseFont = fontService.create(createdMemberId, createDTO, fileDetails); FontUpdateDTO updateDTO = FontUpdateDTO.builder() .name("수정시도") @@ -175,10 +210,14 @@ void getOrThrowByIdFail() { void getFontsSuccess() { // given for (int i = 1; i <= 7; i++) { - fontService.create(existMemberId, FontCreateDTO.builder() - .name("폰트" + i) - .example("예제" + i) - .build()); + fontService.create( + existMemberId, + FontCreateDTO.builder() + .name("폰트" + i) + .example("예제" + i) + .build(), + fileDetails + ); } int page = 0; @@ -244,7 +283,7 @@ void deleteFontAccessDeniedFail() { .example("다른예제") .build(); - Font elseFont = fontService.create(createdMemberId, createDTO); + Font elseFont = fontService.create(createdMemberId, createDTO, fileDetails); // when & then assertThatThrownBy(() -> fontService.delete(existMemberId, elseFont.getId())) @@ -255,20 +294,32 @@ void deleteFontAccessDeniedFail() { @DisplayName("font - getFontPage success test") void getFontPageSuccess() { // given - Font font1 = fontService.create(existMemberId, FontCreateDTO.builder() - .name("페이지폰트1") - .example("예제1") - .build()); + Font font1 = fontService.create( + existMemberId, + FontCreateDTO.builder() + .name("페이지폰트1") + .example("예제1") + .build(), + fileDetails + ); - Font font2 = fontService.create(existMemberId, FontCreateDTO.builder() - .name("페이지폰트2") - .example("예제2") - .build()); + Font font2 = fontService.create( + existMemberId, + FontCreateDTO.builder() + .name("페이지폰트2") + .example("예제2") + .build(), + fileDetails + ); - Font font3 = fontService.create(existMemberId, FontCreateDTO.builder() - .name("페이지폰트") - .example("예제3") - .build()); + Font font3 = fontService.create( + existMemberId, + FontCreateDTO.builder() + .name("페이지폰트") + .example("예제3") + .build(), + fileDetails + ); for (int i = 0; i < 10; i++) { fontService.fontDownload(existMemberId, font1.getId()); @@ -319,14 +370,32 @@ void getOtherFontsSuccess() { @DisplayName("font - getMyPopularFonts success test") void getMyPopularFontsSuccess() { // given - Font font1 = fontService.create(existMemberId, FontCreateDTO.builder() - .name("폰트1").example("예1").build()); - - Font font2 = fontService.create(existMemberId, FontCreateDTO.builder() - .name("폰트2").example("예2").build()); - - Font font3 = fontService.create(existMemberId, FontCreateDTO.builder() - .name("폰트3").example("예3").build()); + Font font1 = fontService.create( + existMemberId, + FontCreateDTO.builder() + .name("폰트1") + .example("예1") + .build(), + fileDetails + ); + + Font font2 = fontService.create( + existMemberId, + FontCreateDTO.builder() + .name("폰트2") + .example("예2") + .build(), + fileDetails + ); + + Font font3 = fontService.create( + existMemberId, + FontCreateDTO.builder() + .name("폰트3") + .example("예3") + .build(), + fileDetails + ); for (int i = 0; i < 5; i++) { fontService.fontDownload(existMemberId, font1.getId()); @@ -360,14 +429,32 @@ void getMyPopularFontsSuccess() { @DisplayName("font - getPopularFonts success test") void getPopularFontsSuccess() { // given - Font font1 = fontService.create(existMemberId, FontCreateDTO.builder() - .name("폰트1").example("예1").build()); + Font font1 = fontService.create( + existMemberId, + FontCreateDTO.builder() + .name("폰트1") + .example("예1") + .build(), + fileDetails + ); - Font font2 = fontService.create(existMemberId, FontCreateDTO.builder() - .name("폰트2").example("예2").build()); + Font font2 = fontService.create( + existMemberId, + FontCreateDTO.builder() + .name("폰트2") + .example("예2") + .build(), + fileDetails + ); - Font font3 = fontService.create(existMemberId, FontCreateDTO.builder() - .name("폰트3").example("예3").build()); + Font font3 = fontService.create( + existMemberId, + FontCreateDTO.builder() + .name("폰트3") + .example("예3") + .build(), + fileDetails + ); for (int i = 0; i < 5; i++) { fontService.fontDownload(existMemberId, font1.getId()); @@ -408,7 +495,7 @@ void updateFontProgressSuccess() { .example("예제입니다") .build(); - Font createdFont = fontService.create(existMemberId, dto); + Font createdFont = fontService.create(existMemberId, dto, fileDetails); FontProgressUpdateDTO progressDto = FontProgressUpdateDTO.builder() .status(FontStatus.DONE)