Compare commits

...

6 Commits

14 changed files with 38 additions and 8 deletions

View File

@@ -99,15 +99,15 @@ tasks.withType<KotlinCompile> {
} }
} }
tasks.withType<Test>().configureEach { tasks.withType<Test> {
maxParallelForks = 1 useJUnitPlatform()
forkEvery = 100
maxHeapSize = "1024m" val springContextCacheMaxSize = (project.findProperty("test.springContextCacheMaxSize") as String?) ?: "1"
maxHeapSize = (project.findProperty("testMaxHeap") as String?) ?: "1536m"
jvmArgs( jvmArgs(
"-XX:MaxMetaspaceSize=512m", "-Dfile.encoding=UTF-8",
"-Dfile.encoding=UTF-8" "-Dspring.test.context.cache.maxSize=$springContextCacheMaxSize"
) )
} }

View File

@@ -186,6 +186,8 @@ data class CreatorChannelCommunityCommentResponse(
val writerProfileImageUrl: String, val writerProfileImageUrl: String,
val writerNickname: String, val writerNickname: String,
val content: String, val content: String,
@JsonProperty("isSecret")
val isSecret: Boolean,
val createdAtUtc: String, val createdAtUtc: String,
val latestReply: CreatorChannelCommunityReplyResponse? val latestReply: CreatorChannelCommunityReplyResponse?
) { ) {
@@ -197,6 +199,7 @@ data class CreatorChannelCommunityCommentResponse(
writerProfileImageUrl = comment.writerProfileImageUrl, writerProfileImageUrl = comment.writerProfileImageUrl,
writerNickname = comment.writerNickname, writerNickname = comment.writerNickname,
content = comment.content, content = comment.content,
isSecret = comment.isSecret,
createdAtUtc = comment.createdAt.toUtcIso(), createdAtUtc = comment.createdAt.toUtcIso(),
latestReply = comment.latestReply?.let(CreatorChannelCommunityReplyResponse::from) latestReply = comment.latestReply?.let(CreatorChannelCommunityReplyResponse::from)
) )
@@ -278,6 +281,7 @@ data class CreatorChannelCommunityComment(
val writerProfileImageUrl: String, val writerProfileImageUrl: String,
val writerNickname: String, val writerNickname: String,
val content: String, val content: String,
val isSecret: Boolean,
val createdAt: LocalDateTime, val createdAt: LocalDateTime,
val latestReply: CreatorChannelCommunityReply? val latestReply: CreatorChannelCommunityReply?
) )
@@ -353,6 +357,7 @@ data class CreatorChannelCommunityCommentRecord(
val writerProfilePath: String?, val writerProfilePath: String?,
val writerNickname: String, val writerNickname: String,
val content: String, val content: String,
val isSecret: Boolean,
val createdAt: LocalDateTime val createdAt: LocalDateTime
) )

View File

@@ -173,6 +173,7 @@ data class CreatorChannelCommunityPostDetailResponse(
- `comments`는 조회자가 볼 수 있는 활성 최상위 댓글 목록이다. - `comments`는 조회자가 볼 수 있는 활성 최상위 댓글 목록이다.
- 댓글 작성자가 조회자와 차단/피차단 관계이면 목록과 전체 개수에서 제외한다. - 댓글 작성자가 조회자와 차단/피차단 관계이면 목록과 전체 개수에서 제외한다.
- 비밀 댓글은 게시물 작성자 또는 댓글 작성자 본인에게만 노출한다. - 비밀 댓글은 게시물 작성자 또는 댓글 작성자 본인에게만 노출한다.
- 각 댓글에는 비밀 댓글 여부인 `isSecret`을 포함한다.
- 댓글 정렬은 최신순 `createdAt desc`, `id desc`를 따른다. - 댓글 정렬은 최신순 `createdAt desc`, `id desc`를 따른다.
- 각 댓글에는 최신 답글 1개를 `latestReply`로 포함한다. - 각 댓글에는 최신 답글 1개를 `latestReply`로 포함한다.
- 최신 답글은 해당 댓글의 활성 답글 중 조회자가 볼 수 있는 답글을 `createdAt desc`, `id desc`로 정렬했을 때 첫 번째 항목이다. - 최신 답글은 해당 댓글의 활성 답글 중 조회자가 볼 수 있는 답글을 `createdAt desc`, `id desc`로 정렬했을 때 첫 번째 항목이다.
@@ -198,6 +199,8 @@ data class CreatorChannelCommunityCommentResponse(
val writerProfileImageUrl: String, val writerProfileImageUrl: String,
val writerNickname: String, val writerNickname: String,
val content: String, val content: String,
@JsonProperty("isSecret")
val isSecret: Boolean,
val createdAtUtc: String, val createdAtUtc: String,
val latestReply: CreatorChannelCommunityReplyResponse? val latestReply: CreatorChannelCommunityReplyResponse?
) )

View File

@@ -81,6 +81,8 @@ data class CreatorChannelCommunityCommentResponse(
val writerProfileImageUrl: String, val writerProfileImageUrl: String,
val writerNickname: String, val writerNickname: String,
val content: String, val content: String,
@JsonProperty("isSecret")
val isSecret: Boolean,
val createdAtUtc: String, val createdAtUtc: String,
val latestReply: CreatorChannelCommunityReplyResponse? val latestReply: CreatorChannelCommunityReplyResponse?
) { ) {
@@ -92,6 +94,7 @@ data class CreatorChannelCommunityCommentResponse(
writerProfileImageUrl = comment.writerProfileImageUrl, writerProfileImageUrl = comment.writerProfileImageUrl,
writerNickname = comment.writerNickname, writerNickname = comment.writerNickname,
content = comment.content, content = comment.content,
isSecret = comment.isSecret,
createdAtUtc = comment.createdAt.toUtcIso(), createdAtUtc = comment.createdAt.toUtcIso(),
latestReply = comment.latestReply?.let(CreatorChannelCommunityReplyResponse::from) latestReply = comment.latestReply?.let(CreatorChannelCommunityReplyResponse::from)
) )

View File

@@ -260,6 +260,7 @@ class DefaultCreatorChannelCommunityQueryRepository(
creatorCommunityComment.member.profileImage, creatorCommunityComment.member.profileImage,
creatorCommunityComment.member.nickname, creatorCommunityComment.member.nickname,
creatorCommunityComment.comment, creatorCommunityComment.comment,
creatorCommunityComment.isSecret,
creatorCommunityComment.createdAt creatorCommunityComment.createdAt
) )
@@ -556,6 +557,7 @@ class DefaultCreatorChannelCommunityQueryRepository(
writerProfilePath = get(creatorCommunityComment.member.profileImage), writerProfilePath = get(creatorCommunityComment.member.profileImage),
writerNickname = get(creatorCommunityComment.member.nickname)!!, writerNickname = get(creatorCommunityComment.member.nickname)!!,
content = get(creatorCommunityComment.comment)!!, content = get(creatorCommunityComment.comment)!!,
isSecret = get(creatorCommunityComment.isSecret)!!,
createdAt = get(creatorCommunityComment.createdAt)!! createdAt = get(creatorCommunityComment.createdAt)!!
) )
} }

View File

@@ -257,6 +257,7 @@ class CreatorChannelCommunityQueryService(
writerProfileImageUrl = writerProfilePath.toCdnUrl(cloudFrontHost) ?: defaultProfileImageUrl(), writerProfileImageUrl = writerProfilePath.toCdnUrl(cloudFrontHost) ?: defaultProfileImageUrl(),
writerNickname = writerNickname.removeDeletedNicknamePrefix(), writerNickname = writerNickname.removeDeletedNicknamePrefix(),
content = content, content = content,
isSecret = isSecret,
createdAt = createdAt, createdAt = createdAt,
latestReply = latestReply latestReply = latestReply
) )

View File

@@ -21,6 +21,7 @@ data class CreatorChannelCommunityComment(
val writerProfileImageUrl: String, val writerProfileImageUrl: String,
val writerNickname: String, val writerNickname: String,
val content: String, val content: String,
val isSecret: Boolean,
val createdAt: LocalDateTime, val createdAt: LocalDateTime,
val latestReply: CreatorChannelCommunityReply? val latestReply: CreatorChannelCommunityReply?
) )

View File

@@ -106,6 +106,7 @@ data class CreatorChannelCommunityCommentRecord(
val writerProfilePath: String?, val writerProfilePath: String?,
val writerNickname: String, val writerNickname: String,
val content: String, val content: String,
val isSecret: Boolean,
val createdAt: LocalDateTime val createdAt: LocalDateTime
) )

View File

@@ -181,6 +181,7 @@ class CreatorChannelCommunityControllerTest @Autowired constructor(
.andExpect(jsonPath("$.data.isLiked").value(true)) .andExpect(jsonPath("$.data.isLiked").value(true))
.andExpect(jsonPath("$.data.comments.commentCount").value(1)) .andExpect(jsonPath("$.data.comments.commentCount").value(1))
.andExpect(jsonPath("$.data.comments.comments[0].writerId").value(20)) .andExpect(jsonPath("$.data.comments.comments[0].writerId").value(20))
.andExpect(jsonPath("$.data.comments.comments[0].isSecret").value(true))
.andExpect(jsonPath("$.data.comments.comments[0].latestReply.commentId").value(202)) .andExpect(jsonPath("$.data.comments.comments[0].latestReply.commentId").value(202))
.andExpect(jsonPath("$.data.comments.comments[0].latestReply.writerId").value(21)) .andExpect(jsonPath("$.data.comments.comments[0].latestReply.writerId").value(21))
} }
@@ -206,6 +207,7 @@ class CreatorChannelCommunityControllerTest @Autowired constructor(
.andExpect(status().isOk) .andExpect(status().isOk)
.andExpect(jsonPath("$.data.commentCount").value(1)) .andExpect(jsonPath("$.data.commentCount").value(1))
.andExpect(jsonPath("$.data.comments[0].writerId").value(20)) .andExpect(jsonPath("$.data.comments[0].writerId").value(20))
.andExpect(jsonPath("$.data.comments[0].isSecret").value(true))
.andExpect(jsonPath("$.data.comments[0].latestReply.commentId").value(202)) .andExpect(jsonPath("$.data.comments[0].latestReply.commentId").value(202))
.andExpect(jsonPath("$.data.comments[0].latestReply.writerId").value(21)) .andExpect(jsonPath("$.data.comments[0].latestReply.writerId").value(21))
.andExpect(jsonPath("$.data.hasNext").value(false)) .andExpect(jsonPath("$.data.hasNext").value(false))
@@ -377,6 +379,7 @@ class CreatorChannelCommunityControllerTest @Autowired constructor(
writerProfileImageUrl = "https://cdn.test/writer.png", writerProfileImageUrl = "https://cdn.test/writer.png",
writerNickname = "writer", writerNickname = "writer",
content = "comment", content = "comment",
isSecret = true,
createdAtUtc = "2026-07-06T03:00:00Z", createdAtUtc = "2026-07-06T03:00:00Z",
latestReply = CreatorChannelCommunityReplyResponse( latestReply = CreatorChannelCommunityReplyResponse(
commentId = 202L, commentId = 202L,

View File

@@ -204,7 +204,9 @@ class CreatorChannelCommunityEndToEndTest @Autowired constructor(
.andExpect(jsonPath("$.data.commentCount").value(2)) .andExpect(jsonPath("$.data.commentCount").value(2))
.andExpect(jsonPath("$.data.comments.length()").value(2)) .andExpect(jsonPath("$.data.comments.length()").value(2))
.andExpect(jsonPath("$.data.comments[0].commentId").value(fixture.ownSecretCommentId)) .andExpect(jsonPath("$.data.comments[0].commentId").value(fixture.ownSecretCommentId))
.andExpect(jsonPath("$.data.comments[0].isSecret").value(true))
.andExpect(jsonPath("$.data.comments[1].commentId").value(fixture.publicCommentId)) .andExpect(jsonPath("$.data.comments[1].commentId").value(fixture.publicCommentId))
.andExpect(jsonPath("$.data.comments[1].isSecret").value(false))
.andExpect(jsonPath("$.data.comments[1].latestReply.commentId").value(fixture.publicReplyId)) .andExpect(jsonPath("$.data.comments[1].latestReply.commentId").value(fixture.publicReplyId))
} }

View File

@@ -107,6 +107,7 @@ class CreatorChannelCommunityFacadeTest {
assertTrue(response.isLiked) assertTrue(response.isLiked)
assertEquals(1, response.comments.commentCount) assertEquals(1, response.comments.commentCount)
assertEquals(20L, response.comments.comments.first().writerId) assertEquals(20L, response.comments.comments.first().writerId)
assertTrue(response.comments.comments.first().isSecret)
assertEquals(21L, response.comments.comments.first().latestReply?.writerId) assertEquals(21L, response.comments.comments.first().latestReply?.writerId)
assertEquals(201L, response.comments.comments.first().latestReply?.commentId) assertEquals(201L, response.comments.comments.first().latestReply?.commentId)
} }
@@ -126,6 +127,7 @@ class CreatorChannelCommunityFacadeTest {
assertEquals(0, response.page) assertEquals(0, response.page)
assertEquals(20, response.size) assertEquals(20, response.size)
assertEquals(20L, response.comments.first().writerId) assertEquals(20L, response.comments.first().writerId)
assertTrue(response.comments.first().isSecret)
assertEquals(21L, response.comments.first().latestReply?.writerId) assertEquals(21L, response.comments.first().latestReply?.writerId)
assertEquals(201L, response.comments.first().latestReply?.commentId) assertEquals(201L, response.comments.first().latestReply?.commentId)
} }
@@ -217,6 +219,7 @@ class CreatorChannelCommunityFacadeTest {
writerProfileImageUrl = "https://cdn.test/writer.png", writerProfileImageUrl = "https://cdn.test/writer.png",
writerNickname = "writer", writerNickname = "writer",
content = "comment", content = "comment",
isSecret = true,
createdAt = LocalDateTime.of(2026, 7, 6, 3, 0), createdAt = LocalDateTime.of(2026, 7, 6, 3, 0),
latestReply = createReplies().replies.first() latestReply = createReplies().replies.first()
) )

View File

@@ -32,6 +32,7 @@ class CreatorChannelCommunityPostDetailResponseTest {
assertTrue(detailJson["comments"]["hasNext"].asBoolean()) assertTrue(detailJson["comments"]["hasNext"].asBoolean())
assertTrue(detailJson["comments"]["comments"][0].has("latestReply")) assertTrue(detailJson["comments"]["comments"][0].has("latestReply"))
assertTrue(detailJson["comments"]["comments"][0].has("writerId")) assertTrue(detailJson["comments"]["comments"][0].has("writerId"))
assertTrue(detailJson["comments"]["comments"][0]["isSecret"].asBoolean())
assertTrue(detailJson["comments"]["comments"][0]["latestReply"].has("commentId")) assertTrue(detailJson["comments"]["comments"][0]["latestReply"].has("commentId"))
assertTrue(detailJson["comments"]["comments"][0]["latestReply"].has("writerId")) assertTrue(detailJson["comments"]["comments"][0]["latestReply"].has("writerId"))
assertTrue(repliesJson["hasNext"].asBoolean()) assertTrue(repliesJson["hasNext"].asBoolean())
@@ -75,6 +76,7 @@ class CreatorChannelCommunityPostDetailResponseTest {
writerProfileImageUrl = "https://cdn.test/comment.png", writerProfileImageUrl = "https://cdn.test/comment.png",
writerNickname = "comment-writer", writerNickname = "comment-writer",
content = "comment", content = "comment",
isSecret = true,
createdAt = LocalDateTime.of(2026, 7, 6, 3, 0), createdAt = LocalDateTime.of(2026, 7, 6, 3, 0),
latestReply = reply latestReply = reply
) )

View File

@@ -195,6 +195,7 @@ class CreatorChannelCommunityQueryServiceTest {
assertEquals(20, detail.comments.comments.size) assertEquals(20, detail.comments.comments.size)
assertEquals(true, detail.comments.hasNext) assertEquals(true, detail.comments.hasNext)
assertEquals(101L, detail.comments.comments.first().writerId) assertEquals(101L, detail.comments.comments.first().writerId)
assertEquals(false, detail.comments.comments.first().isSecret)
assertEquals("reply-writer", detail.comments.comments.first().latestReply?.writerNickname) assertEquals("reply-writer", detail.comments.comments.first().latestReply?.writerNickname)
assertEquals(200L, detail.comments.comments.first().latestReply?.writerId) assertEquals(200L, detail.comments.comments.first().latestReply?.writerId)
assertEquals("https://cdn.test/profile/default-profile.png", detail.comments.comments.first().writerProfileImageUrl) assertEquals("https://cdn.test/profile/default-profile.png", detail.comments.comments.first().writerProfileImageUrl)
@@ -237,6 +238,7 @@ class CreatorChannelCommunityQueryServiceTest {
assertEquals(50, comments.comments.size) assertEquals(50, comments.comments.size)
assertEquals(true, comments.hasNext) assertEquals(true, comments.hasNext)
assertEquals(101L, comments.comments.first().writerId) assertEquals(101L, comments.comments.first().writerId)
assertEquals(false, comments.comments.first().isSecret)
assertEquals("https://cdn.test/profile/1.png", comments.comments.first().writerProfileImageUrl) assertEquals("https://cdn.test/profile/1.png", comments.comments.first().writerProfileImageUrl)
assertEquals(100L, comments.comments[1].latestReply?.commentId) assertEquals(100L, comments.comments[1].latestReply?.commentId)
assertEquals(200L, comments.comments[1].latestReply?.writerId) assertEquals(200L, comments.comments[1].latestReply?.writerId)
@@ -499,7 +501,8 @@ private fun communityCommentRecord(
commentId: Long, commentId: Long,
parentCommentId: Long? = null, parentCommentId: Long? = null,
writerProfilePath: String? = null, writerProfilePath: String? = null,
writerNickname: String = "writer-$commentId" writerNickname: String = "writer-$commentId",
isSecret: Boolean = false
): CreatorChannelCommunityCommentRecord { ): CreatorChannelCommunityCommentRecord {
return CreatorChannelCommunityCommentRecord( return CreatorChannelCommunityCommentRecord(
commentId = commentId, commentId = commentId,
@@ -509,6 +512,7 @@ private fun communityCommentRecord(
writerProfilePath = writerProfilePath, writerProfilePath = writerProfilePath,
writerNickname = writerNickname, writerNickname = writerNickname,
content = "comment-$commentId", content = "comment-$commentId",
isSecret = isSecret,
createdAt = LocalDateTime.of(2026, 7, 6, 10, 0).plusMinutes(commentId) createdAt = LocalDateTime.of(2026, 7, 6, 10, 0).plusMinutes(commentId)
) )
} }

View File

@@ -92,7 +92,7 @@ spring:
datasource: datasource:
driver-class-name: org.h2.Driver driver-class-name: org.h2.Driver
url: jdbc:h2:mem:sodalive-test;MODE=MySQL;DATABASE_TO_UPPER=false;NON_KEYWORDS=VALUE;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE url: jdbc:h2:mem:sodalive-test-${random.uuid};MODE=MySQL;DATABASE_TO_UPPER=false;NON_KEYWORDS=VALUE;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE
username: sa username: sa
password: password: