• 카테고리

    질문 & 답변
  • 세부 분야

    백엔드

  • 해결 여부

    해결됨

EventControllerTest에서 여러 Test가 깨집니다. 부탁드립다..ㅠㅠ

20.03.16 16:39 작성 조회수 261

0

깨진 테스트들

보면 여러 테스트가 깨지는데 에러 소스를 보았습니다.

모든 test의 에러는 같습니다.

인터넷에 찾아보니까 무결성 문제라고 하는데 Before로 모든 account를 deleteAll해놨습니다. 그리고 Account도 생성할때마다 generateId를 지정해놓은 상태입니다. 에러가 뜨는데 updateEvent할때 만든 Account를 before에서 삭제하고 다시 만들어서 사용할텐데 왜 이런 무결성문제가 뜨는지 잘 모르겠습니다.

먼저 선장님 강의를 보면서 처음에 account를 2개 만들고 테스트가 실행되면서 2개가 지워지고 updateEvent를 하면서 id = 3인 account가 생성되고 지워질텐데 에러에 id=4? 란게 있어서 잘 이해가 안됍니다. 도움 부탁드립니다.

org.springframework.dao.DataIntegrityViolationException: could not execute statement; SQL [n/a]; constraint ["FKPHLYW4Y37TCVEHS0E107B93CN: PUBLIC.EVENT FOREIGN KEY(MANAGER_ID) REFERENCES PUBLIC.ACCOUNT(ID) (4)"; SQL statement:
delete from account where id=? [23503-200]]; nested exception is org.hibernate.exception.ConstraintViolationException: could not execute statement
	at org.springframework.orm.jpa.vendor.HibernateJpaDialect.convertHibernateAccessException(HibernateJpaDialect.java:298)
	at org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:255)
	at org.springframework.orm.jpa.JpaTransactionManager.doCommit(JpaTransactionManager.java:538)
	at org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.java:744)
	at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:712)
	at org.springframework.transaction.interceptor.TransactionAspectSupport.commitTransactionAfterReturning(TransactionAspectSupport.java:631)
	at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:385)
	at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:99)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
	at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:139)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
	at org.springframework.data.jpa.repository.support.CrudMethodMetadataPostProcessor$CrudMethodMetadataPopulatingMethodInterceptor.invoke(CrudMethodMetadataPostProcessor.java:178)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
	at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:95)
	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
	at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:212)
	at com.sun.proxy.$Proxy127.deleteAll(Unknown Source)
	at com.hj.spring.events.EventControllerTest.setUp(EventControllerTest.java:58)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:566)
	at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
	at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
	at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
	at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:24)
	at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:75)
	at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:86)
	at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:84)
	at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
	at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:251)
	at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:97)
	at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
	at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
	at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
	at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
	at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
	at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
	at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70)
	at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
	at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:190)
	at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
	at org.junit.runner.JUnitCore.run(JUnitCore.java:115)
	at org.junit.vintage.engine.execution.RunnerExecutor.execute(RunnerExecutor.java:40)
	at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.accept(ForEachOps.java:183)
	at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:195)
	at java.base/java.util.Iterator.forEachRemaining(Iterator.java:133)
	at java.base/java.util.Spliterators$IteratorSpliterator.forEachRemaining(Spliterators.java:1801)
	at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:484)
	at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:474)
	at java.base/java.util.stream.ForEachOps$ForEachOp.evaluateSequential(ForEachOps.java:150)
	at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateSequential(ForEachOps.java:173)
	at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
	at java.base/java.util.stream.ReferencePipeline.forEach(ReferencePipeline.java:497)
	at org.junit.vintage.engine.VintageTestEngine.executeAllChildren(VintageTestEngine.java:80)
	at org.junit.vintage.engine.VintageTestEngine.execute(VintageTestEngine.java:71)
	at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:229)
	at org.junit.platform.launcher.core.DefaultLauncher.lambda$execute$6(DefaultLauncher.java:197)
	at org.junit.platform.launcher.core.DefaultLauncher.withInterceptedStreams(DefaultLauncher.java:211)
	at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:191)
	at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:137)
	at org.eclipse.jdt.internal.junit5.runner.JUnit5TestReference.run(JUnit5TestReference.java:89)
	at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:41)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:542)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:770)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:464)
	at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:210)
Caused by: org.hibernate.exception.ConstraintViolationException: could not execute statement
	at org.hibernate.exception.internal.SQLExceptionTypeDelegate.convert(SQLExceptionTypeDelegate.java:59)
	at org.hibernate.exception.internal.StandardSQLExceptionConverter.convert(StandardSQLExceptionConverter.java:42)
	at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:113)
	at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:99)
	at org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.executeUpdate(ResultSetReturnImpl.java:200)
	at org.hibernate.engine.jdbc.batch.internal.NonBatchingBatch.addToBatch(NonBatchingBatch.java:45)
	at org.hibernate.persister.entity.AbstractEntityPersister.delete(AbstractEntityPersister.java:3542)
	at org.hibernate.persister.entity.AbstractEntityPersister.delete(AbstractEntityPersister.java:3801)
	at org.hibernate.action.internal.EntityDeleteAction.execute(EntityDeleteAction.java:100)
	at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:604)
	at org.hibernate.engine.spi.ActionQueue.lambda$executeActions$1(ActionQueue.java:478)
	at java.base/java.util.LinkedHashMap.forEach(LinkedHashMap.java:684)
	at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:475)
	at org.hibernate.event.internal.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:348)
	at org.hibernate.event.internal.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:40)
	at org.hibernate.event.service.internal.EventListenerGroupImpl.fireEventOnEachListener(EventListenerGroupImpl.java:108)
	at org.hibernate.internal.SessionImpl.doFlush(SessionImpl.java:1344)
	at org.hibernate.internal.SessionImpl.managedFlush(SessionImpl.java:435)
	at org.hibernate.internal.SessionImpl.flushBeforeTransactionCompletion(SessionImpl.java:3221)
	at org.hibernate.internal.SessionImpl.beforeTransactionCompletion(SessionImpl.java:2389)
	at org.hibernate.engine.jdbc.internal.JdbcCoordinatorImpl.beforeTransactionCompletion(JdbcCoordinatorImpl.java:447)
	at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl.beforeCompletionCallback(JdbcResourceLocalTransactionCoordinatorImpl.java:183)
	at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl.access$300(JdbcResourceLocalTransactionCoordinatorImpl.java:40)
	at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl$TransactionDriverControlImpl.commit(JdbcResourceLocalTransactionCoordinatorImpl.java:281)
	at org.hibernate.engine.transaction.internal.TransactionImpl.commit(TransactionImpl.java:101)
	at org.springframework.orm.jpa.JpaTransactionManager.doCommit(JpaTransactionManager.java:534)
	... 64 more
Caused by: org.h2.jdbc.JdbcSQLIntegrityConstraintViolationException: Referential integrity constraint violation: "FKPHLYW4Y37TCVEHS0E107B93CN: PUBLIC.EVENT FOREIGN KEY(MANAGER_ID) REFERENCES PUBLIC.ACCOUNT(ID) (4)"; SQL statement:
delete from account where id=? [23503-200]
	at org.h2.message.DbException.getJdbcSQLException(DbException.java:459)
	at org.h2.message.DbException.getJdbcSQLException(DbException.java:429)
	at org.h2.message.DbException.get(DbException.java:205)
	at org.h2.message.DbException.get(DbException.java:181)
	at org.h2.constraint.ConstraintReferential.checkRow(ConstraintReferential.java:373)
	at org.h2.constraint.ConstraintReferential.checkRowRefTable(ConstraintReferential.java:390)
	at org.h2.constraint.ConstraintReferential.checkRow(ConstraintReferential.java:265)
	at org.h2.table.Table.fireConstraints(Table.java:1057)
	at org.h2.table.Table.fireAfterRow(Table.java:1075)
	at org.h2.command.dml.Delete.update(Delete.java:153)
	at org.h2.command.CommandContainer.update(CommandContainer.java:198)
	at org.h2.command.Command.executeUpdate(Command.java:251)
	at org.h2.jdbc.JdbcPreparedStatement.executeUpdateInternal(JdbcPreparedStatement.java:191)
	at org.h2.jdbc.JdbcPreparedStatement.executeUpdate(JdbcPreparedStatement.java:152)
	at com.zaxxer.hikari.pool.ProxyPreparedStatement.executeUpdate(ProxyPreparedStatement.java:61)
	at com.zaxxer.hikari.pool.HikariProxyPreparedStatement.executeUpdate(HikariProxyPreparedStatement.java)
	at org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.executeUpdate(ResultSetReturnImpl.java:197)
	... 85 more

답변 5

·

답변을 작성해보세요.

1

setUp() 메소드 안에서 데이터를 삭제할 때 Account를 삭제하기 전에 Event의 데이터 부터 삭제해야 합니다. Event가 Account  정보를 FK로 참조하고 있으니까요. setUp() 안에서 순서만 바꾸면 모든 테스트가 잘 동작할 겁니다.

0

선장님 감사합니다. ㅎㅎ

0

0

혹시 코드를 깃헙에 올려서 공유해 주실 수 있으실까요?

0

package com.hj.spring.events;

import static org.springframework.restdocs.headers.HeaderDocumentation.headerWithName;
import static org.springframework.restdocs.headers.HeaderDocumentation.requestHeaders;
import static org.springframework.restdocs.headers.HeaderDocumentation.responseHeaders;
import static org.springframework.restdocs.hypermedia.HypermediaDocumentation.linkWithRel;
import static org.springframework.restdocs.hypermedia.HypermediaDocumentation.links;
import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document;
import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath;
import static org.springframework.restdocs.payload.PayloadDocumentation.relaxedResponseFields;
import static org.springframework.restdocs.payload.PayloadDocumentation.requestFields;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.httpBasic;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

import java.time.LocalDateTime;
import java.util.Set;
import java.util.stream.IntStream;

import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.hateoas.MediaTypes;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.security.oauth2.common.util.Jackson2JsonParser;
import org.springframework.test.web.servlet.ResultActions;

import com.hj.spring.accounts.Account;
import com.hj.spring.accounts.AccountRepository;
import com.hj.spring.accounts.AccountRole;
import com.hj.spring.accounts.AccountService;
import com.hj.spring.common.AppProperties;
import com.hj.spring.common.BaseControllerTest;
import com.hj.spring.common.TestDescription;

public class EventControllerTest  extends BaseControllerTest{
	
	@Autowired
	private EventRepository eventRepository;
	
	@Autowired
	private AccountService accountService;
	
	@Autowired
	private AccountRepository accountRepository;
		
	@Autowired
	private AppProperties appProperties;
	
	@Before
	public void setUp() {
		this.accountRepository.deleteAll();
		this.eventRepository.deleteAll();
	}
	
	@Test
	@TestDescription("정상적으로 이벤트를 생성하는 테스트")
	public void createEvent() throws Exception {
		EventDto eventDto = EventDto.builder()
				.name("Srping")
				.description("Spring API Development with Spring")
				.beginEnrollmentDateTime(LocalDateTime.of(2020, 02, 19, 14, 19))
				.closeEnrollmentDateTime(LocalDateTime.of(2020, 02, 20, 14, 19))
				.beginEventDateTime(LocalDateTime.of(2020, 02, 21, 14, 19))
				.endEventDateTime(LocalDateTime.of(2020, 02, 22, 14, 19))
				.basePrice(100)
				.maxPrice(200)
				.limitOfEnrollment(100)
				.location("Daejeon")
				.build();
		
		mockMvc.perform(post("/api/events/")
				.header(HttpHeaders.AUTHORIZATION, getBearerToken(true))
				.contentType(MediaType.APPLICATION_JSON_UTF8)
				.accept(MediaTypes.HAL_JSON)
				.content(objectMapper.writeValueAsString(eventDto))
				) 
			.andDo(print())
			.andExpect(status().isCreated())
			.andExpect(jsonPath("id").exists())
			.andExpect(header().exists(HttpHeaders.LOCATION))
			.andExpect(header().string(HttpHeaders.CONTENT_TYPE, MediaTypes.HAL_JSON_VALUE))
			.andExpect(jsonPath("free").value(false))
			.andExpect(jsonPath("offline").value(true))
			.andExpect(jsonPath("eventStatus").value(EventStatus.DRAFT.name()))
			.andExpect(jsonPath("_links.self").exists())
			.andExpect(jsonPath("_links.query-events").exists())
			.andExpect(jsonPath("_links.update-event").exists())
			.andDo(document("create-event", 
					links(
							linkWithRel("self").description("link to self"),
							linkWithRel("query-events").description("link to query-events"),
							linkWithRel("update-event").description("link to update an existing"),
							linkWithRel("profile").description("link to profile")
					),
					requestHeaders(
							headerWithName(HttpHeaders.ACCEPT).description("accept header"),
							headerWithName(HttpHeaders.CONTENT_TYPE).description("content type header")
					),
					requestFields(
							fieldWithPath("name").description("Name of new event"),
							fieldWithPath("description").description("Description of new event"),
							fieldWithPath("beginEnrollmentDateTime").description("BeginEnrollmentDateTime of new event"),
							fieldWithPath("closeEnrollmentDateTime").description("CloseEnrollmentDateTime of new event"),
							fieldWithPath("beginEventDateTime").description("BeginEventDateTime of new event"),
							fieldWithPath("endEventDateTime").description("EndEventDateTime of new event"),
							fieldWithPath("location").description("Location of new event"),
							fieldWithPath("basePrice").description("BasePrice of new event"),
							fieldWithPath("maxPrice").description("MaxPrice of new event"),
							fieldWithPath("limitOfEnrollment").description("LimitOfEnrollment of new event")
					),
					responseHeaders(
							headerWithName(HttpHeaders.LOCATION).description("location header"),
							headerWithName(HttpHeaders.CONTENT_TYPE).description("content type header")
					),
					//requestFields를 사용하면 links가 걸린다. 문서화하지 않았기 때문이다. 하지만 relaxed를 사용하면 모든것을 문서화하지 않아도됀다.
					//단점은 정확한 문서를 만들지 못한다는거다.
					//api가 바꼇을때 모든것을 다 하지않으면 api문서화가 제대로 되지 않는다.
					relaxedResponseFields(
							fieldWithPath("id").description("Id of new event"),
							fieldWithPath("name").description("Name of new event"),
							fieldWithPath("description").description("Description of new event"),
							fieldWithPath("beginEnrollmentDateTime").description("BeginEnrollmentDateTime of new event"),
							fieldWithPath("closeEnrollmentDateTime").description("CloseEnrollmentDateTime of new event"),
							fieldWithPath("beginEventDateTime").description("BeginEventDateTime of new event"),
							fieldWithPath("endEventDateTime").description("EndEventDateTime of new event"),
							fieldWithPath("location").description("Location of new event"),
							fieldWithPath("basePrice").description("BasePrice of new event"),
							fieldWithPath("maxPrice").description("MaxPrice of new event"),
							fieldWithPath("limitOfEnrollment").description("LimitOfEnrollment of new event"),
							fieldWithPath("free").description("Free of new event"),
							fieldWithPath("offline").description("Offline of new event"),
							fieldWithPath("eventStatus").description("EventStatus of new event"),
							fieldWithPath("_links.self.href").description("link to self"),
							fieldWithPath("_links.query-events.href").description("link to query-event list"),
							fieldWithPath("_links.update-event.href").description("link to update existing event"),
							fieldWithPath("_links.profile.href").description("link to profile")
							
					)
			))
			;
	}
	
	@Test
	@TestDescription("입력 받을 수 없는 값을 사용한 경우에 에러가 발생하는 테스트")
	public void createEvent_Bad_Request_Unknown_Input() throws Exception {
		Event event = Event.builder()
				.id(100)
				.name("Srping")
				.description("REST API Development with Spring")
				.beginEnrollmentDateTime(LocalDateTime.of(2020, 02, 19, 14, 19))
				.closeEnrollmentDateTime(LocalDateTime.of(2020, 02, 20, 14, 19))
				.beginEventDateTime(LocalDateTime.of(2020, 02, 21, 14, 19))
				.endEventDateTime(LocalDateTime.of(2020, 02, 22, 14, 19))
				.basePrice(100)
				.maxPrice(200)
				.limitOfEnrollment(100)
				.location("대전 현지의 러브하우스")
				.eventStatus(EventStatus.PUBLISHED)
				.free(true)
				.offline(false)
				.build();
		
		mockMvc.perform(post("/api/events/")
				.header(HttpHeaders.AUTHORIZATION, getBearerToken(true))
				.contentType(MediaType.APPLICATION_JSON_UTF8)
				.accept(MediaTypes.HAL_JSON)
				.content(objectMapper.writeValueAsString(event))
				) 
			.andDo(print())
			.andExpect(status().isBadRequest())
			;
			
	}
	
	@Test
	@TestDescription("입력값이 비어있는 경우에 에러가 발생하는 테스트")
	public void createEvent_Bad_Request_Empty_Input() throws Exception {
		EventDto eventDto = EventDto.builder().build();
		
		mockMvc.perform(post("/api/events/")
				.header(HttpHeaders.AUTHORIZATION, getBearerToken(true))
				.contentType(MediaType.APPLICATION_JSON_UTF8)
				.accept(MediaTypes.HAL_JSON)
				.content(objectMapper.writeValueAsString(eventDto))
				)
				.andExpect(status().isBadRequest())
				.andDo(print())
				.andExpect(jsonPath("_links.index").exists())
				;
	}
	
	@Test
	@TestDescription("입력 값이 잘못된 경우에 에러가 발생하는 테스트")
	public void createEvent_Bad_Request_Wrong_Input() throws Exception {
		EventDto eventDto = EventDto.builder()
				.name("Srping")
				.description("REST API Development with Spring")
				.beginEnrollmentDateTime(LocalDateTime.of(2020, 02, 22, 14, 19))
				.closeEnrollmentDateTime(LocalDateTime.of(2020, 02, 21, 14, 19))
				.beginEventDateTime(LocalDateTime.of(2020, 02, 20, 14, 19))
				.endEventDateTime(LocalDateTime.of(2020, 02, 19, 14, 19))
				.basePrice(10000)
				.maxPrice(200)
				.limitOfEnrollment(100)
				.location("대전 현지의 러브하우스")
				.build();
		
		mockMvc.perform(post("/api/events/")
				.header(HttpHeaders.AUTHORIZATION, getBearerToken(true))
				.contentType(MediaType.APPLICATION_JSON_UTF8)
				.accept(MediaTypes.HAL_JSON)
				.content(objectMapper.writeValueAsString(eventDto))
				)
				.andDo(print())
				.andExpect(status().isBadRequest())
				.andExpect(jsonPath("content.[0].objectName").exists())
				.andExpect(jsonPath("content.[0].defaultMessage").exists())
				.andExpect(jsonPath("content.[0].code").exists())
				.andExpect(jsonPath("_links.index").exists())
				;
	}
	
	@Test
	@TestDescription("30개의 이벤트를 10개씩 조회하는데 11~20 조회하기")
	public void queryEvent() throws Exception{
		// Given
		IntStream.range(1, 31).forEach(i -> {
			this.generatedEvent(i);
		});
		
		this.mockMvc.perform(get("/api/events/")
				.param("page", "1")
				.param("size", "10")
				.param("sort", "id,DESC")
			)
			.andDo(print())
			.andExpect(status().isOk())
			.andExpect(jsonPath("page").exists())
			.andExpect(jsonPath("_embedded.eventList[0]._links.self").exists())
			.andExpect(jsonPath("_links.self").exists())
			.andExpect(jsonPath("_links.profile").exists())
			.andDo(document("query-events"))
			;
	}
	
	@Test
	@TestDescription("30개의 이벤트를 10개씩 조회하는데 11~20 조회하기 With Authentication")
	public void queryEventWithAuthentication() throws Exception{
		// Given
		IntStream.range(1, 31).forEach(i -> {
			this.generatedEvent(i);
		});
		
		this.mockMvc.perform(get("/api/events/")
				.header(HttpHeaders.AUTHORIZATION, getBearerToken(true))
				.param("page", "1")
				.param("size", "10")
				.param("sort", "id,DESC")
			)
			.andDo(print())
			.andExpect(status().isOk())
			.andExpect(jsonPath("page").exists())
			.andExpect(jsonPath("_embedded.eventList[0]._links.self").exists())
			.andExpect(jsonPath("_links.self").exists())
			.andExpect(jsonPath("_links.profile").exists())
			.andExpect(jsonPath("_links.create-event").exists())
			.andDo(document("query-events"))
			;
	}
	
	@Test
	@TestDescription("기존의 이벤트 하나 조회하기")
	public void getEvent() throws Exception {
		// Given
		Account account = this.createAccount();
		Event event = this.generatedEvent(100, account);
		// When
		this.mockMvc.perform(get("/api/events/"+event.getId()))
			.andExpect(status().isOk())
			.andDo(print())
			.andExpect(jsonPath("id").exists())
			.andExpect(jsonPath("name").exists())
			.andExpect(jsonPath("_links.self").exists())
			.andExpect(jsonPath("_links.profile").exists())
			.andDo(document("get-an-event"))
			;
	}
	
	@Test
	@TestDescription("없는 이벤트를 조회했을때 404응답받기")
	public void getEvent404() throws Exception {
		// When
		this.mockMvc.perform(get("/api/events/123"))
			.andExpect(status().isNotFound())
			;
	}
	
	@Test
	@TestDescription("이벤트를 정상적으로 수정하기")
	public void updateEvent() throws Exception {
		// Given
		Account account = createAccount();
		Event event = this.generatedEvent(200, account);
		
		EventDto eventDto = modelMapper.map(event, EventDto.class);
		String eventName = "Update event";
		eventDto.setName(eventName);
		
		// When
		this.mockMvc.perform(put("/api/events/{id}",event.getId())
					.header(HttpHeaders.AUTHORIZATION, getBearerToken(false))
					.contentType(MediaType.APPLICATION_JSON_UTF8)
					.content(objectMapper.writeValueAsString(eventDto))
					.accept(MediaTypes.HAL_JSON)
				)
			.andDo(print())
			.andExpect(status().isOk())
			.andExpect(jsonPath("name").value(eventName))
			.andExpect(jsonPath("_links.self").exists())
			.andDo(document("update-event"))
			;
	}
	
	@Test
	@TestDescription("입력값이 비어있는 경우 이벤트 수정 실패")
	public void updateEvent_400_Empty() throws Exception {
		//Given
		Event event = this.generatedEvent(200);
		EventDto eventDto = new EventDto();
		
		this.mockMvc.perform(put("/api/events/{id}",event.getId())
				.header(HttpHeaders.AUTHORIZATION, getBearerToken(true))
				.contentType(MediaType.APPLICATION_JSON_UTF8)
				.content(objectMapper.writeValueAsString(eventDto))
				.accept(MediaTypes.HAL_JSON)
			)
			.andDo(print())
			.andExpect(status().isBadRequest())
		;
	}
	
	@Test
	@TestDescription("입력값이 잘못된 경우 이벤트 수정 실패")
	public void updateEvent_400_Wrong() throws Exception {
		//Given
		Event event = this.generatedEvent(200);
		EventDto eventDto = modelMapper.map(event, EventDto.class);
		eventDto.setBasePrice(20000);
		eventDto.setMaxPrice(10000);
		
		this.mockMvc.perform(put("/api/events/{id}",event.getId())
				.header(HttpHeaders.AUTHORIZATION, getBearerToken(true))
				.contentType(MediaType.APPLICATION_JSON_UTF8)
				.content(objectMapper.writeValueAsString(eventDto))
				.accept(MediaTypes.HAL_JSON)
			)
			.andDo(print())
			.andExpect(status().isBadRequest())
		;
	}
	
	@Test
	@TestDescription("존재하지 않는 이벤트 수정")
	public void updateEvent_404() throws Exception {
		//Given
		Event event = this.generatedEvent(200);
		EventDto eventDto = modelMapper.map(event, EventDto.class);
		
		this.mockMvc.perform(put("/api/events/12341234")
				.header(HttpHeaders.AUTHORIZATION, getBearerToken(true))
				.contentType(MediaType.APPLICATION_JSON_UTF8)
				.content(objectMapper.writeValueAsString(eventDto))
				.accept(MediaTypes.HAL_JSON)
			)
			.andDo(print())
			.andExpect(status().isNotFound())
		;
	}
	
	private Event generatedEvent(int i, Account account) {
		Event event = BuildEvent(i);
		event.setManager(account);
		return this.eventRepository.save(event);
	}
	
	private Event generatedEvent(int i) {
		Event event = BuildEvent(i);
		return this.eventRepository.save(event);
	}
	
	private Event BuildEvent(int i) {
		
		Event event = Event.builder()
				.name("Srping")
				.description("Spring API Development with Spring")
				.beginEnrollmentDateTime(LocalDateTime.of(2020, 02, 19, 14, 19))
				.closeEnrollmentDateTime(LocalDateTime.of(2020, 02, 20, 14, 19))
				.beginEventDateTime(LocalDateTime.of(2020, 02, 21, 14, 19))
				.endEventDateTime(LocalDateTime.of(2020, 02, 22, 14, 19))
				.basePrice(100)
				.maxPrice(200)
				.limitOfEnrollment(100)
				.location("Daejeon")
				.free(false)
				.offline(true)
				.eventStatus(EventStatus.DRAFT)
				.build();
		return event;
	}
	
	private String getBearerToken(boolean needToCreateAccount) throws Exception {
		return "Bearer " + getAcessToken(needToCreateAccount);
	}
	
	private Account createAccount() {
		Account khj = Account.builder()
				.email(appProperties.getAdminUsername())
				.password(appProperties.getAdminPassword())
				.roles(Set.of(AccountRole.ADMIN, AccountRole.USER))
				.build();
					
		return this.accountService.saveAccount(khj);
	}
	
	private String getAcessToken(boolean needToCreateAccount) throws Exception {
		if(needToCreateAccount) {
			createAccount();		
		}
		
		ResultActions perform = this.mockMvc.perform(post("/oauth/token")
			.with(httpBasic(appProperties.getClientId(), appProperties.getClientSecret()))	
			.param("username", appProperties.getAdminUsername())
			.param("password", appProperties.getAdminPassword())
			.param("grant_type", "password")
		)
			.andDo(print())
			.andExpect(status().isOk())
			.andExpect(jsonPath("access_token").exists())
		;
		
		var responseBody =  perform.andReturn().getResponse().getContentAsString();
		Jackson2JsonParser parser = new Jackson2JsonParser();
		return parser.parseMap(responseBody).get("access_token").toString();
		
	}
}