diff --git a/README.md b/README.md index a4f7992..fa87a4e 100644 --- a/README.md +++ b/README.md @@ -1 +1,23 @@ -# spring-shopping-precourse \ No newline at end of file +# spring-shopping-precourse + +## 기능사항 + + +### 상품 +- [x] 상품이름은 공백 포함 최대 15자이다. +- [x] 특수문자 (),[],+,-,&,/,_만 가능하다. +- [x] 상품을 조회할 수 있다. +- [x] 상품을 추가할 수 있다. +- [ ] 상품을 수정할 수 있다. +- [ ] 상품을 삭제할 수 있다. +- [ ] 비속어를 포함할 수 없다. + + +### 개발 진행 +1. TDD 형태로 개발할 것 +2. 작은 단계부터 큰 단계로 진행 +- [x] 모델 +- [ ] 서비스 +- [ ] 컨트롤러 +- [ ] 레포지토리 +- [ ] DB 연결 \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index 3f75395..98bb6f3 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -33,6 +33,8 @@ dependencies { testImplementation("org.springframework.boot:spring-boot-starter-test") testImplementation("org.jetbrains.kotlin:kotlin-test-junit5") testRuntimeOnly("org.junit.platform:junit-platform-launcher") + + testImplementation ("org.assertj:assertj-core:3.11.1") } kotlin { diff --git a/src/main/java/shopping/api/entity/CreateProductRequest.java b/src/main/java/shopping/api/entity/CreateProductRequest.java new file mode 100644 index 0000000..abb860f --- /dev/null +++ b/src/main/java/shopping/api/entity/CreateProductRequest.java @@ -0,0 +1,30 @@ +package shopping.api.entity; + +import shopping.domain.entity.ImageUrl; +import shopping.domain.entity.Name; +import shopping.domain.entity.Price; + +public class CreateProductRequest { + + private Name name; + private Price price; + private ImageUrl imageUrl; + + public CreateProductRequest(Name name, Price price, ImageUrl imageUrl) { + this.name = name; + this.price = price; + this.imageUrl = imageUrl; + } + + public Name getName() { + return name; + } + + public Price getPrice() { + return price; + } + + public ImageUrl getImageUrl() { + return imageUrl; + } +} diff --git a/src/main/java/shopping/api/service/ProductService.java b/src/main/java/shopping/api/service/ProductService.java new file mode 100644 index 0000000..dab47dd --- /dev/null +++ b/src/main/java/shopping/api/service/ProductService.java @@ -0,0 +1,32 @@ +package shopping.api.service; + +import org.springframework.stereotype.Service; +import shopping.api.entity.CreateProductRequest; +import shopping.domain.entity.Product; +import shopping.domain.util.IDGenarator; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.atomic.AtomicLong; + +@Service +public class ProductService { + + private final Map database = new HashMap<>(); + + public Product save(CreateProductRequest createProductRequest) { + long id = IDGenarator.generate(); + + Product product = new Product(id, createProductRequest.getName(), createProductRequest.getPrice(), createProductRequest.getImageUrl()); + database.put(id, product); + + return product; + } + + public Product findById(Long id) throws RuntimeException{ + if(database.isEmpty()) + throw new RuntimeException("등록된 상품이 없습니다."); + + return database.get(id); + } +} diff --git a/src/main/java/shopping/domain/entity/ImageUrl.java b/src/main/java/shopping/domain/entity/ImageUrl.java new file mode 100644 index 0000000..a454b84 --- /dev/null +++ b/src/main/java/shopping/domain/entity/ImageUrl.java @@ -0,0 +1,22 @@ +package shopping.domain.entity; + +import java.util.regex.Pattern; + +public class ImageUrl { + + private final String regex = "^(https?:\\/\\/.*\\.(png|jpg))$"; + private final Pattern pattern = Pattern.compile(regex); + + private String value; + + public ImageUrl(String value) throws IllegalArgumentException{ + if(!pattern.matcher(value).matches()) + throw new IllegalArgumentException("잘못된 이미지 경로입니다."); + + this.value = value; + } + + public String getValue() { + return value; + } +} diff --git a/src/main/java/shopping/domain/entity/Name.java b/src/main/java/shopping/domain/entity/Name.java new file mode 100644 index 0000000..8745034 --- /dev/null +++ b/src/main/java/shopping/domain/entity/Name.java @@ -0,0 +1,21 @@ +package shopping.domain.entity; + +import java.util.regex.Pattern; + +public class Name { + + private final String regx = "^[a-zA-Z0-9가-힣 ()\\[\\]+\\-&/_]{1,15}$"; + private final Pattern pattern = Pattern.compile(regx); + private String value; + + public Name(String value) throws IllegalArgumentException{ + if(!pattern.matcher(value).matches()) + throw new IllegalArgumentException("상품 명이 올바르지 않습니다."); + + this.value = value; + } + + public String getValue() { + return value; + } +} diff --git a/src/main/java/shopping/domain/entity/Price.java b/src/main/java/shopping/domain/entity/Price.java new file mode 100644 index 0000000..ec5bc06 --- /dev/null +++ b/src/main/java/shopping/domain/entity/Price.java @@ -0,0 +1,16 @@ +package shopping.domain.entity; + +public class Price { + + private int value; + + public Price(int value) throws IllegalArgumentException{ + if(value < 0) + throw new IllegalArgumentException("상품 가격은 음수가 될 수 없습니다."); + this.value = value; + } + + public int getValue() { + return value; + } +} diff --git a/src/main/java/shopping/domain/entity/Product.java b/src/main/java/shopping/domain/entity/Product.java new file mode 100644 index 0000000..e06aae8 --- /dev/null +++ b/src/main/java/shopping/domain/entity/Product.java @@ -0,0 +1,47 @@ +package shopping.domain.entity; + +import java.util.concurrent.atomic.AtomicLong; + +public class Product { + + private Long id; + private Name name; + private Price price; + private ImageUrl imageUrl; + + public Product(Long id, Product product) { + this.id = id; + this.name = product.getName(); + this.price = product.getPrice(); + this.imageUrl = product.getImageUrl(); + } + + public Product(Long id, Name name, Price price, ImageUrl imageUrl) { + this.id = id; + this.name = name; + this.price = price; + this.imageUrl = imageUrl; + } + + public Product(Name name, Price price, ImageUrl imageUrl) { + this.name = name; + this.price = price; + this.imageUrl = imageUrl; + } + + public Long getId() { + return id; + } + + public Name getName() { + return name; + } + + public Price getPrice() { + return price; + } + + public ImageUrl getImageUrl() { + return imageUrl; + } +} diff --git a/src/main/java/shopping/domain/util/IDGenarator.java b/src/main/java/shopping/domain/util/IDGenarator.java new file mode 100644 index 0000000..d7706d2 --- /dev/null +++ b/src/main/java/shopping/domain/util/IDGenarator.java @@ -0,0 +1,13 @@ +package shopping.domain.util; + +import java.util.concurrent.atomic.AtomicLong; + +public class IDGenarator { + + private static final AtomicLong id = new AtomicLong(1); + + public static long generate() { + return id.getAndIncrement(); + } + +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index c33078c..5ba017b 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1 +1 @@ -spring.application.name=spring-shopping +spring.application.name=spring-shopping \ No newline at end of file diff --git a/src/main/resources/schema.sql b/src/main/resources/schema.sql new file mode 100644 index 0000000..b548312 --- /dev/null +++ b/src/main/resources/schema.sql @@ -0,0 +1,6 @@ +create table products ( + id long auto_increment primary key , + name varchar(15), + price int, + image_url varchar(1000) +); \ No newline at end of file diff --git a/src/test/java/shopping/api/entity/ImageUrlTest.java b/src/test/java/shopping/api/entity/ImageUrlTest.java new file mode 100644 index 0000000..5145197 --- /dev/null +++ b/src/test/java/shopping/api/entity/ImageUrlTest.java @@ -0,0 +1,18 @@ +package shopping.api.entity; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import shopping.domain.entity.ImageUrl; + +import static org.assertj.core.api.Assertions.*; + +public class ImageUrlTest { + + @Test + @DisplayName("이미지 경로는 http://나 https://로 시작하고 이미지 확장자로 끝나야 한다. (.png, ~~~)") + void constructorTest() { + +// assertThat(new ImageUrl("www.naver.com").getValue()).isEqualTo(new ImageUrl("www.naver.com").getValue()); + assertThatIllegalArgumentException().isThrownBy(() -> new ImageUrl("http://www.naver.com/image")); + } +} diff --git a/src/test/java/shopping/api/entity/NameTest.java b/src/test/java/shopping/api/entity/NameTest.java new file mode 100644 index 0000000..9e5f609 --- /dev/null +++ b/src/test/java/shopping/api/entity/NameTest.java @@ -0,0 +1,24 @@ +package shopping.api.entity; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import shopping.domain.entity.Name; + +import static org.assertj.core.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class NameTest { + + @Test + @DisplayName("이름은 15자를 넘으면 안되고, 특수문자를 일부 허용한다.") + void constructorTest() { + try { + new Name("안녕하세요감사해요잘있어요다시만나요안녕하세요"); + } catch (IllegalArgumentException e) { + System.out.println(e.getMessage()); + assertEquals("상품 명이 올바르지 않습니다.", e.getMessage()); + } + + } +} diff --git a/src/test/java/shopping/api/entity/ProductTest.java b/src/test/java/shopping/api/entity/ProductTest.java new file mode 100644 index 0000000..c95e40b --- /dev/null +++ b/src/test/java/shopping/api/entity/ProductTest.java @@ -0,0 +1,28 @@ +package shopping.api.entity; + + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import shopping.domain.entity.ImageUrl; +import shopping.domain.entity.Name; +import shopping.domain.entity.Price; +import shopping.domain.entity.Product; + +import static org.assertj.core.api.Assertions.*; + + +public class ProductTest { + + @Test + @DisplayName("상품은 이름, 가격, 이미지를 가지고 있어야 한다.") + void constructorTest() { + Product product = new Product(new Name("책"), new Price(2000), new ImageUrl("https://image.png")); + + assertThat(product.getName().getValue()).isEqualTo(new Name("책").getValue()); + assertThat(product.getPrice().getValue()).isEqualTo(new Price(2000).getValue()); + assertThat(product.getImageUrl().getValue()).isEqualTo(new ImageUrl("https://image.png").getValue()); + + } + + +} diff --git a/src/test/java/shopping/api/service/ProductServiceTest.java b/src/test/java/shopping/api/service/ProductServiceTest.java new file mode 100644 index 0000000..1bd230a --- /dev/null +++ b/src/test/java/shopping/api/service/ProductServiceTest.java @@ -0,0 +1,46 @@ +package shopping.api.service; + +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 shopping.api.entity.CreateProductRequest; +import shopping.domain.entity.ImageUrl; +import shopping.domain.entity.Name; +import shopping.domain.entity.Price; +import shopping.domain.entity.Product; + +import static org.assertj.core.api.Assertions.*; + +@SpringBootTest +public class ProductServiceTest { + + @Autowired + private ProductService productService; + + @Test + @DisplayName("상품을 조회한다.") + void findProductTest() { + Product product = 상품_생성("책",2000,"http://image.png"); + + Product actual = productService.findById(product.getId()); + + assertThat(actual.getName().getValue()).isEqualTo(product.getName().getValue()); + assertThat(actual.getPrice().getValue()).isEqualTo(product.getPrice().getValue()); + assertThat(actual.getImageUrl().getValue()).isEqualTo(product.getImageUrl().getValue()); + } + + + Product 상품_생성(String name, int price, String imageUrl) { + CreateProductRequest createProductRequest = new CreateProductRequest(new Name(name), new Price(price), new ImageUrl(imageUrl)); + + + Product savedProduct = productService.save(createProductRequest); + + assertThat(savedProduct.getName().getValue()).isEqualTo(name); + assertThat(savedProduct.getPrice().getValue()).isEqualTo(price); + assertThat(savedProduct.getImageUrl().getValue()).isEqualTo(imageUrl); + + return savedProduct; + } +}