Skip to content

Commit

Permalink
feat: News, Content 엔티티 정의
Browse files Browse the repository at this point in the history
  • Loading branch information
seokjin8678 committed Dec 4, 2023
1 parent dc4798e commit a38bc10
Show file tree
Hide file tree
Showing 11 changed files with 427 additions and 0 deletions.
52 changes: 52 additions & 0 deletions src/main/kotlin/kr/galaxyhub/sc/news/domain/Content.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package kr.galaxyhub.sc.news.domain

import jakarta.persistence.Column
import jakarta.persistence.Embedded
import jakarta.persistence.Entity
import jakarta.persistence.EnumType
import jakarta.persistence.Enumerated
import jakarta.persistence.FetchType
import jakarta.persistence.GeneratedValue
import jakarta.persistence.GenerationType
import jakarta.persistence.Id
import jakarta.persistence.JoinColumn
import jakarta.persistence.Lob
import jakarta.persistence.ManyToOne

@Entity
class Content(
newsInformation: NewsInformation,
language: Language,
content: String,
) {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
val sequence: Long? = null

@ManyToOne(fetch = FetchType.LAZY, optional = false)
@JoinColumn(name = "news_id", nullable = false)
var news: News? = null
private set

@Embedded
var newsInformation: NewsInformation = newsInformation
private set

@Enumerated(EnumType.STRING)
@Column(name = "language", nullable = false)
var language: Language = language
private set

@Lob
@Column(name = "content", nullable = false)
var content: String = content
private set

fun initialNews(news: News) {
if (this.news != null) {
throw IllegalArgumentException("이미 뉴스에 등록된 컨텐츠 입니다.") // TODO 명확한 예외 정의할 것
}
this.news = news
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package kr.galaxyhub.sc.news.domain

import jakarta.persistence.AttributeConverter
import jakarta.persistence.Converter
import java.util.EnumSet

@Converter
class EnumSetLanguageConverter : AttributeConverter<EnumSet<Language>, String> {

override fun convertToDatabaseColumn(attribute: EnumSet<Language>): String {
return attribute.joinToString(separator = SEPARATOR)
}

override fun convertToEntityAttribute(dbData: String?): EnumSet<Language> {
return if (dbData.isNullOrBlank()) {
EnumSet.noneOf(Language::class.java)
} else {
dbData.split(SEPARATOR)
.map { Language.valueOf(it) }
.toCollection(EnumSet.noneOf(Language::class.java))
}
}

companion object {

private const val SEPARATOR = ","
}
}
6 changes: 6 additions & 0 deletions src/main/kotlin/kr/galaxyhub/sc/news/domain/Language.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package kr.galaxyhub.sc.news.domain

enum class Language {
ENGLISH,
KOREAN,
}
60 changes: 60 additions & 0 deletions src/main/kotlin/kr/galaxyhub/sc/news/domain/News.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package kr.galaxyhub.sc.news.domain

import jakarta.persistence.CascadeType
import jakarta.persistence.Column
import jakarta.persistence.Convert
import jakarta.persistence.Embedded
import jakarta.persistence.Entity
import jakarta.persistence.EnumType
import jakarta.persistence.Enumerated
import jakarta.persistence.FetchType
import jakarta.persistence.OneToMany
import java.time.LocalDateTime
import java.util.EnumSet
import kr.galaxyhub.sc.common.domain.PrimaryKeyEntity

@Entity
class News(
newsType: NewsType,
originId: Long,
publishedAt: LocalDateTime,
) : PrimaryKeyEntity() {

@Enumerated(EnumType.STRING)
@Column(name = "news_type", nullable = false)
var newsType: NewsType = newsType
private set

@Column(name = "origin_id", nullable = false)
var originId: Long = originId
private set

@Column(name = "published_at", nullable = false)
var publishedAt: LocalDateTime = publishedAt
private set

@Embedded
var newsInformation: NewsInformation = NewsInformation.EMPTY
private set

@Convert(converter = EnumSetLanguageConverter::class)
@Column(name = "support_languages", nullable = false)
private val mutableSupportLanguages: EnumSet<Language> = EnumSet.noneOf(Language::class.java)
val supportLanguages: Set<Language> get() = mutableSupportLanguages.toHashSet()

@OneToMany(fetch = FetchType.LAZY, cascade = [CascadeType.PERSIST], mappedBy = "news")
private val contents: MutableList<Content> = mutableListOf()

fun addContent(content: Content) {
val language = content.language
if (mutableSupportLanguages.contains(language)) {
throw IllegalArgumentException("이미 해당 언어로 작성된 뉴스가 있습니다.") // TODO 명확한 예외 정의할 것
}
if (mutableSupportLanguages.isEmpty()) {
newsInformation = content.newsInformation
}
contents.add(content)
content.initialNews(this)
mutableSupportLanguages.add(language)
}
}
19 changes: 19 additions & 0 deletions src/main/kotlin/kr/galaxyhub/sc/news/domain/NewsInformation.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package kr.galaxyhub.sc.news.domain

import jakarta.persistence.Column
import jakarta.persistence.Embeddable

@Embeddable
data class NewsInformation(
@Column(name = "title", nullable = true)
val title: String?,

@Column(name = "excerpt", nullable = true)
val excerpt: String?
) {
companion object {

val EMPTY = NewsInformation(null, null)
}
}

6 changes: 6 additions & 0 deletions src/main/kotlin/kr/galaxyhub/sc/news/domain/NewsType.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package kr.galaxyhub.sc.news.domain

enum class NewsType {
PATCH_NOTE,
NEWS,
}
46 changes: 46 additions & 0 deletions src/test/kotlin/kr/galaxyhub/sc/news/domain/ContentTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package kr.galaxyhub.sc.news.domain

import io.kotest.assertions.throwables.shouldThrow
import io.kotest.core.spec.style.DescribeSpec
import io.kotest.matchers.shouldBe
import io.kotest.matchers.throwable.shouldHaveMessage
import kr.galaxyhub.sc.news.fixture.ContentFixture
import kr.galaxyhub.sc.news.fixture.NewsFixture

class ContentTest : DescribeSpec({

describe("constructor") {
context("컨텐츠가 생성되면") {
val content = ContentFixture.of(Language.ENGLISH)

it("뉴스는 null이다.") {
content.news shouldBe null
}
}
}

describe("initialNews") {
context("컨텐츠가 뉴스에 등록된 상태이면") {
val content = ContentFixture.of(Language.ENGLISH)
content.initialNews(NewsFixture.create())

it("IllegalArgumentException 예외를 던진다.") {
val exception = shouldThrow<IllegalArgumentException> {
content.initialNews(NewsFixture.create())
}
exception shouldHaveMessage "이미 뉴스에 등록된 컨텐츠 입니다."
}
}

context("컨텐츠가 뉴스에 등록된 상태가 아니면") {
val content = ContentFixture.of(Language.ENGLISH)

it("성공적으로 등록된다.") {
val news = NewsFixture.create()
content.initialNews(news)

content.news shouldBe news
}
}
}
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package kr.galaxyhub.sc.news.domain

import io.kotest.assertions.assertSoftly
import io.kotest.core.spec.style.DescribeSpec
import io.kotest.inspectors.forAll
import io.kotest.matchers.collections.shouldContainExactly
import io.kotest.matchers.collections.shouldHaveSize
import io.kotest.matchers.shouldBe
import io.kotest.matchers.string.shouldContain
import java.util.EnumSet

class EnumSetLanguageConverterTest : DescribeSpec({

val enumSetLanguageConverter = EnumSetLanguageConverter()

describe("convertToDatabaseColumn") {
context("요소가 한 개 이면") {
val attribute = EnumSet.of(Language.ENGLISH)

it(",가 없는 문자열을 반환한다.") {
val expect = enumSetLanguageConverter.convertToDatabaseColumn(attribute)

expect shouldBe "ENGLISH"
}
}

context("요소가 여러 개 이면") {
val attribute = EnumSet.of(Language.ENGLISH, Language.KOREAN)

it(",가 있는 문자열을 반환한다.") {
val expect = enumSetLanguageConverter.convertToDatabaseColumn(attribute)

assertSoftly {
expect shouldContain ","
expect shouldContain "ENGLISH"
expect shouldContain "KOREAN"
}
}
}

context("요소가 없으면") {
val attribute = EnumSet.noneOf(Language::class.java)

it("빈 문자열을 반환한다.") {
val expect = enumSetLanguageConverter.convertToDatabaseColumn(attribute)

expect shouldBe ""
}
}
}

describe("convertToEntityAttribute") {
context(",가 없는 문자열이면") {
val dbData = "ENGLISH"

it("한 개의 요소를 반환한다.") {
val expect = enumSetLanguageConverter.convertToEntityAttribute(dbData)

assertSoftly {
expect shouldHaveSize 1
expect shouldContainExactly setOf(Language.ENGLISH)
}
}
}

context(",가 있는 문자열이면") {
val dbData = "ENGLISH,KOREAN"

it("여러 개의 요소를 반환한다.") {
val expect = enumSetLanguageConverter.convertToEntityAttribute(dbData)

assertSoftly {
expect shouldHaveSize 2
expect shouldContainExactly setOf(Language.ENGLISH, Language.KOREAN)
}
}
}

context("null 또는 빈 문자열이면") {
val dbDate = listOf(null, "", " ", "\t", "\n")

it("비어 있는 요소를 반환한다.") {
dbDate.forAll {
val expect = enumSetLanguageConverter.convertToEntityAttribute(it)

expect shouldHaveSize 0
}
}
}
}
})
77 changes: 77 additions & 0 deletions src/test/kotlin/kr/galaxyhub/sc/news/domain/NewsTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package kr.galaxyhub.sc.news.domain

import io.kotest.assertions.assertSoftly
import io.kotest.assertions.throwables.shouldThrow
import io.kotest.core.spec.style.DescribeSpec
import io.kotest.matchers.collections.shouldContainExactly
import io.kotest.matchers.collections.shouldHaveSize
import io.kotest.matchers.shouldBe
import io.kotest.matchers.shouldNotBe
import io.kotest.matchers.throwable.shouldHaveMessage
import kr.galaxyhub.sc.news.fixture.ContentFixture
import kr.galaxyhub.sc.news.fixture.NewsFixture

class NewsTest : DescribeSpec({

describe("constructor") {
context("뉴스가 생성되면") {
val news = NewsFixture.create()

it("supportLanguages는 비어있다.") {
news.supportLanguages shouldHaveSize 0
}

it("newsInformation은 null이 아닌, Empty이다.") {
assertSoftly {
news.newsInformation shouldNotBe null
news.newsInformation shouldBe NewsInformation.EMPTY
}
}
}
}

describe("addContent") {
context("뉴스에 컨텐츠가 추가된 상태에서") {
val news = NewsFixture.create()
news.addContent(ContentFixture.of(Language.ENGLISH))

context("중복된 언어의 컨텐츠가 추가되면") {
it("IllegalArgumentException 예외를 던진다.") {
val exception = shouldThrow<IllegalArgumentException> {
news.addContent(ContentFixture.of(Language.ENGLISH))
}
exception shouldHaveMessage "이미 해당 언어로 작성된 뉴스가 있습니다."
}
}

context("중복되지 않은 언어의 컨텐츠가 추가되면") {
val content = ContentFixture.of(Language.KOREAN)
news.addContent(content)

it("성공적으로 추가된다.") {
news.supportLanguages shouldContainExactly setOf(Language.KOREAN, Language.ENGLISH)
}

it("newsInformation은 변경되지 않는다.") {
news.newsInformation shouldNotBe content.newsInformation
}
}
}

context("뉴스에 컨텐츠가 없는 상태에서") {
val news = NewsFixture.create()
context("컨텐츠가 추가되면") {
val content = ContentFixture.of(Language.ENGLISH)
news.addContent(content)

it("성공적으로 추가된다.") {
news.supportLanguages shouldContainExactly setOf(Language.ENGLISH)
}

it("newsInformation이 설정된다.") {
news.newsInformation shouldBe content.newsInformation
}
}
}
}
})
Loading

0 comments on commit a38bc10

Please sign in to comment.