From 70b985d3870d8e4d058eeeb983ff77cf2d78f238 Mon Sep 17 00:00:00 2001 From: Wouter Nederhof Date: Sun, 14 Apr 2024 22:47:21 +0200 Subject: [PATCH] WIP on fixing tests of Java --- .../graphql/user/UserDataLoader.java.peb | 33 +++ .../graphql/user/UserResolver.java.peb | 103 +++++++ .../security/SecurityConfig.java.peb | 67 +++++ .../security/UserDetailsServiceImpl.java.peb | 26 ++ .../security/WebSecurityConfig.java.peb | 43 +++ .../domain/[nameLowerCase]/User.java.peb | 20 ++ .../[nameLowerCase]/UserDetailsImpl.java.peb | 49 ++++ .../[nameLowerCase]/UserEntity.java.peb | 27 ++ .../domain/[nameLowerCase]/UserEvent.java.peb | 44 +++ .../[nameLowerCase]/UserListener.java.peb | 24 ++ .../[nameLowerCase]/UserRepository.java.peb | 20 ++ .../[nameLowerCase]/UserService.java.peb | 179 ++++++++++++ ...ionPrefix]__create_[nameSnakeCase].sql.peb | 7 + .../schema/[namePluralCamelCase].graphqls.peb | 34 +++ .../application/FakeSecurityConfig.java.peb | 19 ++ .../application/ResolverTestContext.java.peb | 59 ++++ .../graphql/user/UserResolverTests.java.peb | 157 ++++++++++ .../[nameLowerCase]/UserFixtures.java.peb | 42 +++ .../UserIntegrationTests.java.peb | 66 +++++ .../UserListenerUnitTests.java.peb | 29 ++ .../UserServiceUnitTests.java.peb | 270 ++++++++++++++++++ .../[nameLowerCase]/[namePascalCase].java.peb | 8 +- .../[namePascalCase]Entity.java.peb | 8 +- .../[namePascalCase]Resolver.java.peb | 5 +- .../[namePascalCase]ResolverTests.java.peb | 2 +- .../[artifactIdSlashes]/Application.java.peb | 2 + .../[namePascalCase]Event.java.peb | 2 + .../[namePascalCase]Service.java.peb | 8 +- .../[namePascalCase]ServiceUnitTests.java.peb | 6 +- 29 files changed, 1345 insertions(+), 14 deletions(-) create mode 100644 pkg/generator/templates/backends/java/auth/[artifactId]-server/src/main/java/[groupIdSlashes]/[artifactIdSlashes]/application/graphql/user/UserDataLoader.java.peb create mode 100644 pkg/generator/templates/backends/java/auth/[artifactId]-server/src/main/java/[groupIdSlashes]/[artifactIdSlashes]/application/graphql/user/UserResolver.java.peb create mode 100644 pkg/generator/templates/backends/java/auth/[artifactId]-server/src/main/java/[groupIdSlashes]/[artifactIdSlashes]/application/security/SecurityConfig.java.peb create mode 100644 pkg/generator/templates/backends/java/auth/[artifactId]-server/src/main/java/[groupIdSlashes]/[artifactIdSlashes]/application/security/UserDetailsServiceImpl.java.peb create mode 100644 pkg/generator/templates/backends/java/auth/[artifactId]-server/src/main/java/[groupIdSlashes]/[artifactIdSlashes]/application/security/WebSecurityConfig.java.peb create mode 100644 pkg/generator/templates/backends/java/auth/[artifactId]-server/src/main/java/[groupIdSlashes]/[artifactIdSlashes]/domain/[nameLowerCase]/User.java.peb create mode 100644 pkg/generator/templates/backends/java/auth/[artifactId]-server/src/main/java/[groupIdSlashes]/[artifactIdSlashes]/domain/[nameLowerCase]/UserDetailsImpl.java.peb create mode 100644 pkg/generator/templates/backends/java/auth/[artifactId]-server/src/main/java/[groupIdSlashes]/[artifactIdSlashes]/domain/[nameLowerCase]/UserEntity.java.peb create mode 100644 pkg/generator/templates/backends/java/auth/[artifactId]-server/src/main/java/[groupIdSlashes]/[artifactIdSlashes]/domain/[nameLowerCase]/UserEvent.java.peb create mode 100644 pkg/generator/templates/backends/java/auth/[artifactId]-server/src/main/java/[groupIdSlashes]/[artifactIdSlashes]/domain/[nameLowerCase]/UserListener.java.peb create mode 100644 pkg/generator/templates/backends/java/auth/[artifactId]-server/src/main/java/[groupIdSlashes]/[artifactIdSlashes]/domain/[nameLowerCase]/UserRepository.java.peb create mode 100644 pkg/generator/templates/backends/java/auth/[artifactId]-server/src/main/java/[groupIdSlashes]/[artifactIdSlashes]/domain/[nameLowerCase]/UserService.java.peb create mode 100644 pkg/generator/templates/backends/java/auth/[artifactId]-server/src/main/resources/db/migration/V[nextMigrationPrefix]__create_[nameSnakeCase].sql.peb create mode 100644 pkg/generator/templates/backends/java/auth/[artifactId]-server/src/main/resources/schema/[namePluralCamelCase].graphqls.peb create mode 100644 pkg/generator/templates/backends/java/auth/[artifactId]-server/src/test/java/[groupIdSlashes]/[artifactIdSlashes]/application/FakeSecurityConfig.java.peb create mode 100644 pkg/generator/templates/backends/java/auth/[artifactId]-server/src/test/java/[groupIdSlashes]/[artifactIdSlashes]/application/ResolverTestContext.java.peb create mode 100644 pkg/generator/templates/backends/java/auth/[artifactId]-server/src/test/java/[groupIdSlashes]/[artifactIdSlashes]/application/graphql/user/UserResolverTests.java.peb create mode 100644 pkg/generator/templates/backends/java/auth/[artifactId]-server/src/test/java/[groupIdSlashes]/[artifactIdSlashes]/domain/[nameLowerCase]/UserFixtures.java.peb create mode 100644 pkg/generator/templates/backends/java/auth/[artifactId]-server/src/test/java/[groupIdSlashes]/[artifactIdSlashes]/domain/[nameLowerCase]/UserIntegrationTests.java.peb create mode 100644 pkg/generator/templates/backends/java/auth/[artifactId]-server/src/test/java/[groupIdSlashes]/[artifactIdSlashes]/domain/[nameLowerCase]/UserListenerUnitTests.java.peb create mode 100644 pkg/generator/templates/backends/java/auth/[artifactId]-server/src/test/java/[groupIdSlashes]/[artifactIdSlashes]/domain/[nameLowerCase]/UserServiceUnitTests.java.peb diff --git a/pkg/generator/templates/backends/java/auth/[artifactId]-server/src/main/java/[groupIdSlashes]/[artifactIdSlashes]/application/graphql/user/UserDataLoader.java.peb b/pkg/generator/templates/backends/java/auth/[artifactId]-server/src/main/java/[groupIdSlashes]/[artifactIdSlashes]/application/graphql/user/UserDataLoader.java.peb new file mode 100644 index 0000000..cf6b820 --- /dev/null +++ b/pkg/generator/templates/backends/java/auth/[artifactId]-server/src/main/java/[groupIdSlashes]/[artifactIdSlashes]/application/graphql/user/UserDataLoader.java.peb @@ -0,0 +1,33 @@ +package {{ groupId }}.{{ artifactId }}.application.graphql.{{ nameLowerCase }}; + +import com.netflix.graphql.dgs.DgsComponent; +import com.netflix.graphql.dgs.DgsDataLoader; +import {{ groupId }}.{{ artifactId }}.domain.{{ nameLowerCase }}.{{ namePascalCase }}; +import {{ groupId }}.{{ artifactId }}.domain.{{ nameLowerCase }}.{{ namePascalCase }}Service; +import lombok.RequiredArgsConstructor; +import org.dataloader.MappedBatchLoader; +{%if hasRelations%} +import java.util.List;{%endif%} +import java.util.function.Function; +import java.util.stream.Collectors; + +import static java.util.concurrent.CompletableFuture.supplyAsync; + +@DgsComponent +@RequiredArgsConstructor +public class {{ namePascalCase }}DataLoader { + private {{ namePascalCase }}Service {{ nameCamelCase }}Service; + + @DgsDataLoader(name = "{{ nameCamelCase }}ById") + MappedBatchLoader {{ nameCamelCase }}ById = ids -> supplyAsync(() -> + {{ nameCamelCase }}Service.findByIds(ids) + .stream() + .collect(Collectors.toMap({{ namePascalCase }}::getId, Function.identity()))); +{%for field in fields%}{%if field.isFieldRelational%} + @DgsDataLoader(name = "{{ namePluralCamelCase }}By{{ field.fieldNamePascalCase }}") + MappedBatchLoader> {{ namePluralCamelCase }}By{{ field.fieldNamePascalCase }} = ids -> supplyAsync(() -> + {{ nameCamelCase }}Service.findBy{{ field.fieldNamePluralPascalCase }}(ids) + .stream() + .collect(Collectors.groupingBy({{ namePascalCase }}::get{{ field.fieldNamePascalCase }}))); +{%endif%}{%endfor%} +} diff --git a/pkg/generator/templates/backends/java/auth/[artifactId]-server/src/main/java/[groupIdSlashes]/[artifactIdSlashes]/application/graphql/user/UserResolver.java.peb b/pkg/generator/templates/backends/java/auth/[artifactId]-server/src/main/java/[groupIdSlashes]/[artifactIdSlashes]/application/graphql/user/UserResolver.java.peb new file mode 100644 index 0000000..206318e --- /dev/null +++ b/pkg/generator/templates/backends/java/auth/[artifactId]-server/src/main/java/[groupIdSlashes]/[artifactIdSlashes]/application/graphql/user/UserResolver.java.peb @@ -0,0 +1,103 @@ +package {{ groupId }}.{{ artifactId }}.application.graphql.{{ nameLowerCase }}; + +import {{ groupId }}.{{ artifactId }}.domain.{{ nameLowerCase }}.{{ namePascalCase }}; +import {{ groupId }}.{{ artifactId }}.domain.{{ nameLowerCase }}.{{ namePascalCase }}Service; +import {{ groupId }}.{{ artifactId }}.domain.{{ nameLowerCase }}.{{ namePascalCase }}Service.Update{{ namePascalCase }}Input; +import {{ groupId }}.{{ artifactId }}.domain.{{ nameLowerCase }}.{{ namePascalCase }}Service.LoginInput; +import {{ groupId }}.{{ artifactId }}.domain.{{ nameLowerCase }}.{{ namePascalCase }}Service.RegisterInput;{%if hasRelations%} +import {{ groupId }}.{{ artifactId }}.domain.{{ nameLowerCase }}.{{ namePascalCase }}Service.{{ namePascalCase }}FilterInput;{%endif%} +{%for field in fields%}{%if field.isFieldRelational%}import {{ groupId }}.{{ artifactId }}.domain.{{ field.fieldTypeLowerCase }}.{{ field.fieldTypePascalCase }}; +{%endif%}{%endfor%}import com.netflix.graphql.dgs.DgsComponent;{%if hasRelations%} +import com.netflix.graphql.dgs.DgsData;{%endif%} +import com.netflix.graphql.dgs.DgsQuery; +import com.netflix.graphql.dgs.DgsMutation;{%if hasRelations%} +import com.netflix.graphql.dgs.DgsDataFetchingEnvironment;{%endif%} +import lombok.AllArgsConstructor;{%if hasRelations%} +import java.util.Objects;{%endif%} +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.authentication.AuthenticationProvider; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.core.context.SecurityContextHolder; +import java.util.Optional; +{%if hasRelations%}import java.util.concurrent.CompletableFuture; + +import static java.util.Collections.emptyList;{%endif%} + +@DgsComponent +@AllArgsConstructor +public class {{ namePascalCase }}Resolver { + private {{ namePascalCase }}Service {{ nameCamelCase }}Service; + private AuthenticationProvider authenticationProvider; + + @DgsQuery + Optional currentUser() { + var context = SecurityContextHolder.getContext(); + if (context.getAuthentication() == null) { + return Optional.empty(); + } + var username = context.getAuthentication().getName(); + return userService.findByUsername(username); + } + + @DgsQuery + Optional<{{ namePascalCase }}> {{ nameCamelCase }}(int id) { + return {{ nameCamelCase }}Service.findById(id); + } + + @DgsQuery + Iterable<{{ namePascalCase }}> {{ namePluralCamelCase }}({%if hasRelations%}{{ namePascalCase }}FilterInput filter{%endif%}) { + if (filter == null) { + return {{ nameCamelCase }}Service.findAll(); + } + return {{ nameCamelCase }}Service.findAll({%if hasRelations%}filter{%endif%}); + } + + @DgsMutation + Optional login(LoginInput input) { + try { + var credentials = new UsernamePasswordAuthenticationToken(input.getUsername(), input.getPassword()); + SecurityContextHolder.getContext().setAuthentication(authenticationProvider.authenticate(credentials)); + return currentUser(); + } catch (AuthenticationException e) { + return Optional.empty(); + } + } + + @DgsMutation + boolean logout() { + SecurityContextHolder.getContext().setAuthentication(null); + return true; + } + + @DgsMutation + User register(RegisterInput input) { + return userService.register(input); + } + + @DgsMutation + Optional<{{ namePascalCase }}> update{{ namePascalCase }}(int id, Update{{ namePascalCase }}Input input) { + return {{ nameCamelCase }}Service.update(id, input); + } + + @DgsMutation + boolean delete{{ namePascalCase }}(int id) { + return {{ nameCamelCase }}Service.deleteById(id); + } +{%for field in fields%}{%if field.isFieldRelational%} + @DgsData(parentType = "{{ namePascalCase }}") + CompletableFuture<{{ field.fieldTypePascalCase }}> {{ field.fieldTypeCamelCase }}(DgsDataFetchingEnvironment dfe) { + var {{ nameCamelCase }} = dfe.<{{ namePascalCase }}>getSource(); + return dfe.getDataLoader("{{ field.fieldTypeCamelCase }}ById") + .load({{ nameCamelCase }}.get{{ field.fieldNamePascalCase }}()); + } + + @DgsData(parentType = "{{ field.fieldTypePascalCase }}", field = "{{ namePluralCamelCase }}") + CompletableFuture> {{ namePluralCamelCase }}By{{ field.fieldTypePascalCase }}(DgsDataFetchingEnvironment dfe) { + var {{ field.fieldTypeCamelCase }} = dfe.<{{ namePascalCase }}>getSource(); + var {{ field.fieldNameCamelCase }} = Objects.requireNonNull({{ field.fieldTypeCamelCase }}).getId(); + return dfe.>getDataLoader("{{ namePluralCamelCase }}By{{ field.fieldNamePascalCase }}") + .load({{ field.fieldNameCamelCase }}) + .thenApply(it -> it == null ? emptyList() : it); + } +{%endif%}{%endfor%} +} diff --git a/pkg/generator/templates/backends/java/auth/[artifactId]-server/src/main/java/[groupIdSlashes]/[artifactIdSlashes]/application/security/SecurityConfig.java.peb b/pkg/generator/templates/backends/java/auth/[artifactId]-server/src/main/java/[groupIdSlashes]/[artifactIdSlashes]/application/security/SecurityConfig.java.peb new file mode 100644 index 0000000..4ea7aab --- /dev/null +++ b/pkg/generator/templates/backends/java/auth/[artifactId]-server/src/main/java/[groupIdSlashes]/[artifactIdSlashes]/application/security/SecurityConfig.java.peb @@ -0,0 +1,67 @@ +package {{ groupId }}.{{ artifactId }}.application.security; + +import org.jetbrains.annotations.NotNull; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Primary; +import org.springframework.core.task.TaskDecorator; +import org.springframework.scheduling.annotation.AsyncConfigurer; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; +import org.springframework.security.authentication.AuthenticationProvider; +import org.springframework.security.authentication.dao.DaoAuthenticationProvider; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.web.context.request.RequestContextHolder; + +import java.util.concurrent.Executor; + +@Configuration +public class SecurityConfig implements AsyncConfigurer { + + @Bean + PasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(10); + } + + @Bean + AuthenticationProvider authenticationProvider( + UserDetailsService userDetailsService, + PasswordEncoder passwordEncoder + ) { + var provider = new DaoAuthenticationProvider(); + provider.setUserDetailsService(userDetailsService); + provider.setPasswordEncoder(passwordEncoder); + return provider; + } + + + @Bean + @Primary + @Override + public Executor getAsyncExecutor() { + var poolExecutor = new ThreadPoolTaskExecutor(); + poolExecutor.setTaskDecorator(new ContextCopyingDecorator()); + poolExecutor.initialize(); + return poolExecutor; + } + + public static class ContextCopyingDecorator implements TaskDecorator { + @Override + public @NotNull Runnable decorate(@NotNull Runnable runnable) { + var context = RequestContextHolder.currentRequestAttributes(); + var securityContext = SecurityContextHolder.getContext(); + return () -> { + try { + RequestContextHolder.setRequestAttributes(context); + SecurityContextHolder.setContext(securityContext); + runnable.run(); + } finally { + RequestContextHolder.resetRequestAttributes(); + SecurityContextHolder.clearContext(); + } + }; + } + } +} diff --git a/pkg/generator/templates/backends/java/auth/[artifactId]-server/src/main/java/[groupIdSlashes]/[artifactIdSlashes]/application/security/UserDetailsServiceImpl.java.peb b/pkg/generator/templates/backends/java/auth/[artifactId]-server/src/main/java/[groupIdSlashes]/[artifactIdSlashes]/application/security/UserDetailsServiceImpl.java.peb new file mode 100644 index 0000000..e90951d --- /dev/null +++ b/pkg/generator/templates/backends/java/auth/[artifactId]-server/src/main/java/[groupIdSlashes]/[artifactIdSlashes]/application/security/UserDetailsServiceImpl.java.peb @@ -0,0 +1,26 @@ +package {{ groupId }}.{{ artifactId }}.application.security; + +import {{ groupId }}.{{ artifactId }}.domain.user.UserService; +import lombok.RequiredArgsConstructor; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@Transactional +@RequiredArgsConstructor +class UserDetailsServiceImpl implements UserDetailsService { + private final UserService userService; + + @Override + public UserDetails loadUserByUsername(String username) { + var userDetails = userService.findUserDetailsByUsername(username); + if (userDetails.isEmpty()) { + throw new UsernameNotFoundException("Username not found: " + username); + } + return userDetails.get(); + } + +} diff --git a/pkg/generator/templates/backends/java/auth/[artifactId]-server/src/main/java/[groupIdSlashes]/[artifactIdSlashes]/application/security/WebSecurityConfig.java.peb b/pkg/generator/templates/backends/java/auth/[artifactId]-server/src/main/java/[groupIdSlashes]/[artifactIdSlashes]/application/security/WebSecurityConfig.java.peb new file mode 100644 index 0000000..2b1b0d5 --- /dev/null +++ b/pkg/generator/templates/backends/java/auth/[artifactId]-server/src/main/java/[groupIdSlashes]/[artifactIdSlashes]/application/security/WebSecurityConfig.java.peb @@ -0,0 +1,43 @@ +package {{ groupId }}.{{ artifactId }}.application.security; + +import lombok.RequiredArgsConstructor; +import org.springframework.boot.autoconfigure.security.SecurityProperties; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.authentication.AuthenticationProvider; +import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; +import org.springframework.security.web.SecurityFilterChain; + +import static org.springframework.security.config.http.SessionCreationPolicy.IF_REQUIRED; + +@Configuration +@EnableWebSecurity +@EnableMethodSecurity +@EnableConfigurationProperties(SecurityProperties.class) +@RequiredArgsConstructor +class WebSecurityConfig { + private final AuthenticationProvider authenticationProvider; + + @Bean + SecurityFilterChain filterChain(HttpSecurity http) throws Exception { + return http + .authenticationProvider(authenticationProvider) + .cors(AbstractHttpConfigurer::disable) + .csrf(AbstractHttpConfigurer::disable) + .authorizeHttpRequests(customizer -> + customizer + .requestMatchers("/graphql").permitAll() + .requestMatchers("/actuator/health").permitAll() + .requestMatchers("/subscriptions").permitAll() + .requestMatchers("/graphiql/**").permitAll() + .requestMatchers("/api/**").permitAll() + ) + .sessionManagement(it -> it.sessionCreationPolicy(IF_REQUIRED)) + .securityContext(it -> it.requireExplicitSave(false)) + .build(); + } +} diff --git a/pkg/generator/templates/backends/java/auth/[artifactId]-server/src/main/java/[groupIdSlashes]/[artifactIdSlashes]/domain/[nameLowerCase]/User.java.peb b/pkg/generator/templates/backends/java/auth/[artifactId]-server/src/main/java/[groupIdSlashes]/[artifactIdSlashes]/domain/[nameLowerCase]/User.java.peb new file mode 100644 index 0000000..3e22c91 --- /dev/null +++ b/pkg/generator/templates/backends/java/auth/[artifactId]-server/src/main/java/[groupIdSlashes]/[artifactIdSlashes]/domain/[nameLowerCase]/User.java.peb @@ -0,0 +1,20 @@ +package {{ groupId }}.{{ artifactId }}.domain.{{ nameLowerCase }}; + +import lombok.Builder; +import lombok.Data; +import lombok.NonNull; +import lombok.With; + +import java.time.OffsetDateTime; + +@Data +@With +@Builder +public class {{ namePascalCase }} { + private final int id;{%for field in fields%} + @NonNull + private final {{ field.fieldJavaType }} {{ field.fieldNameCamelCase }};{%endfor%} + @NonNull + private final OffsetDateTime createdAt; + private final OffsetDateTime updatedAt; +} diff --git a/pkg/generator/templates/backends/java/auth/[artifactId]-server/src/main/java/[groupIdSlashes]/[artifactIdSlashes]/domain/[nameLowerCase]/UserDetailsImpl.java.peb b/pkg/generator/templates/backends/java/auth/[artifactId]-server/src/main/java/[groupIdSlashes]/[artifactIdSlashes]/domain/[nameLowerCase]/UserDetailsImpl.java.peb new file mode 100644 index 0000000..1d3a3ce --- /dev/null +++ b/pkg/generator/templates/backends/java/auth/[artifactId]-server/src/main/java/[groupIdSlashes]/[artifactIdSlashes]/domain/[nameLowerCase]/UserDetailsImpl.java.peb @@ -0,0 +1,49 @@ +package {{ groupId }}.{{ artifactId }}.domain.{{ nameLowerCase }}; + +import lombok.RequiredArgsConstructor; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; + +import java.util.Collections; +import java.util.List; + +@RequiredArgsConstructor +public class UserDetailsImpl implements UserDetails { + private final UserEntity userEntity; + + @Override + public List getAuthorities() { + return Collections.singletonList(new SimpleGrantedAuthority("USER")); + } + + @Override + public String getPassword() { + return userEntity.getPassword(); + } + + @Override + public String getUsername() { + return userEntity.getUsername(); + } + + @Override + public boolean isAccountNonExpired() { + return true; + } + + @Override + public boolean isAccountNonLocked() { + return true; + } + + @Override + public boolean isCredentialsNonExpired() { + return true; + } + + @Override + public boolean isEnabled() { + return true; + } +} diff --git a/pkg/generator/templates/backends/java/auth/[artifactId]-server/src/main/java/[groupIdSlashes]/[artifactIdSlashes]/domain/[nameLowerCase]/UserEntity.java.peb b/pkg/generator/templates/backends/java/auth/[artifactId]-server/src/main/java/[groupIdSlashes]/[artifactIdSlashes]/domain/[nameLowerCase]/UserEntity.java.peb new file mode 100644 index 0000000..f2eb572 --- /dev/null +++ b/pkg/generator/templates/backends/java/auth/[artifactId]-server/src/main/java/[groupIdSlashes]/[artifactIdSlashes]/domain/[nameLowerCase]/UserEntity.java.peb @@ -0,0 +1,27 @@ +package {{ groupId }}.{{ artifactId }}.domain.{{ nameLowerCase }}; + +import lombok.Builder; +import lombok.Data; +import lombok.NonNull; +import lombok.With; +import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.annotation.Id; +import org.springframework.data.annotation.LastModifiedDate; +import org.springframework.data.relational.core.mapping.Table; + +import java.time.LocalDateTime; + +@Data +@With +@Builder +@Table("{{ namePluralSnakeCase }}") +public class {{ namePascalCase }}Entity { + @Id + private final Integer id;{%for field in fields%} + @NonNull + private final {{ field.fieldJavaType }} {{ field.fieldNameCamelCase }};{%endfor%} + @CreatedDate + private final LocalDateTime createdAt; + @LastModifiedDate + private final LocalDateTime updatedAt; +} diff --git a/pkg/generator/templates/backends/java/auth/[artifactId]-server/src/main/java/[groupIdSlashes]/[artifactIdSlashes]/domain/[nameLowerCase]/UserEvent.java.peb b/pkg/generator/templates/backends/java/auth/[artifactId]-server/src/main/java/[groupIdSlashes]/[artifactIdSlashes]/domain/[nameLowerCase]/UserEvent.java.peb new file mode 100644 index 0000000..ad2696d --- /dev/null +++ b/pkg/generator/templates/backends/java/auth/[artifactId]-server/src/main/java/[groupIdSlashes]/[artifactIdSlashes]/domain/[nameLowerCase]/UserEvent.java.peb @@ -0,0 +1,44 @@ +package {{ groupId }}.{{ artifactId }}.domain.{{ nameLowerCase }}; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import org.springframework.context.ApplicationEvent; + +import java.util.List; + +@EqualsAndHashCode(callSuper = false) +public abstract class {{ namePascalCase }}Event extends ApplicationEvent { + {{ namePascalCase }}Event(Object source) { + super(source); + } + + @Getter + public static class {{ namePluralPascalCase }}RegisteredEvent extends {{ namePascalCase }}Event { + private final List<{{ namePascalCase }}> entities; + + public {{ namePluralPascalCase }}RegisteredEvent(Object source, List<{{ namePascalCase }}> entities) { + super(source); + this.entities = entities; + } + } + + @Getter + public static class {{ namePluralPascalCase }}UpdatedEvent extends {{ namePascalCase }}Event { + private final List<{{ namePascalCase }}> entities; + + public {{ namePluralPascalCase }}UpdatedEvent(Object source, List<{{ namePascalCase }}> entities) { + super(source); + this.entities = entities; + } + } + + @Getter + public static class {{ namePluralPascalCase }}DeletedEvent extends {{ namePascalCase }}Event { + private final List<{{ namePascalCase }}> entities; + + public {{ namePluralPascalCase }}DeletedEvent(Object source, List<{{ namePascalCase }}> entities) { + super(source); + this.entities = entities; + } + } +} diff --git a/pkg/generator/templates/backends/java/auth/[artifactId]-server/src/main/java/[groupIdSlashes]/[artifactIdSlashes]/domain/[nameLowerCase]/UserListener.java.peb b/pkg/generator/templates/backends/java/auth/[artifactId]-server/src/main/java/[groupIdSlashes]/[artifactIdSlashes]/domain/[nameLowerCase]/UserListener.java.peb new file mode 100644 index 0000000..eb9b4b7 --- /dev/null +++ b/pkg/generator/templates/backends/java/auth/[artifactId]-server/src/main/java/[groupIdSlashes]/[artifactIdSlashes]/domain/[nameLowerCase]/UserListener.java.peb @@ -0,0 +1,24 @@ +{%if hasRelations%}package {{ groupId }}.{{ artifactId }}.domain.{{ nameLowerCase }}; +{%for field in fields%}{%if field.isFieldRelational%} +import {{ groupId }}.{{ artifactId }}.domain.{{ field.fieldTypeLowerCase }}.{{ field.fieldTypePascalCase }}; +import {{ groupId }}.{{ artifactId }}.domain.{{ field.fieldTypeLowerCase }}.{{ field.fieldTypePascalCase }}Event.{{ field.fieldTypePluralPascalCase }}DeletedEvent;{%endif%}{%endfor%} +import lombok.AllArgsConstructor; +import org.springframework.context.event.EventListener; +import org.springframework.stereotype.Component; + +import java.util.stream.Collectors; + +@Component +@AllArgsConstructor +class {{ namePascalCase }}Listener { + private {{ namePascalCase }}Service {{ nameCamelCase }}Service; +{%for field in fields%}{%if field.isFieldRelational%} + @EventListener + void onEvent({{ field.fieldTypePluralPascalCase }}DeletedEvent event) { + var entityIds = event.getEntities().stream() + .map({{ field.fieldTypePascalCase }}::getId) + .collect(Collectors.toList()); + {{ nameCamelCase }}Service.deleteBy{{ field.fieldNamePluralPascalCase }}(entityIds); + } +{%endif%}{%endfor%} +}{%endif%} diff --git a/pkg/generator/templates/backends/java/auth/[artifactId]-server/src/main/java/[groupIdSlashes]/[artifactIdSlashes]/domain/[nameLowerCase]/UserRepository.java.peb b/pkg/generator/templates/backends/java/auth/[artifactId]-server/src/main/java/[groupIdSlashes]/[artifactIdSlashes]/domain/[nameLowerCase]/UserRepository.java.peb new file mode 100644 index 0000000..754ae0a --- /dev/null +++ b/pkg/generator/templates/backends/java/auth/[artifactId]-server/src/main/java/[groupIdSlashes]/[artifactIdSlashes]/domain/[nameLowerCase]/UserRepository.java.peb @@ -0,0 +1,20 @@ +package {{ groupId }}.{{ artifactId }}.domain.{{ nameLowerCase }}; + +import org.springframework.data.jdbc.repository.query.Query; +import org.springframework.data.repository.CrudRepository; +import org.springframework.data.repository.query.Param; + +import java.util.List; +import java.util.Optional; + +public interface {{ namePascalCase }}Repository extends CrudRepository<{{ namePascalCase }}Entity, Integer> { + + @Query("SELECT * FROM {{ namePluralSnakeCase }} WHERE id IN (:ids)") + List<{{ namePascalCase }}Entity> findByIds(@Param("ids") Iterable ids); +{%for field in fields%}{%if field.isFieldRelational%} + @Query("SELECT * FROM {{ namePluralSnakeCase }} WHERE {{ field.fieldNameSnakeCase }} IN (:{{ field.fieldNamePluralCamelCase }})") + List<{{ namePascalCase }}Entity> findBy{{ field.fieldNamePluralPascalCase }}(@Param("{{ field.fieldNamePluralCamelCase }}") Iterable {{ field.fieldNamePluralCamelCase }}); +{%endif%}{%endfor%} + @Query("SELECT * FROM users WHERE username = :username") + Optional findByUsername(@Param("username") String username); +} diff --git a/pkg/generator/templates/backends/java/auth/[artifactId]-server/src/main/java/[groupIdSlashes]/[artifactIdSlashes]/domain/[nameLowerCase]/UserService.java.peb b/pkg/generator/templates/backends/java/auth/[artifactId]-server/src/main/java/[groupIdSlashes]/[artifactIdSlashes]/domain/[nameLowerCase]/UserService.java.peb new file mode 100644 index 0000000..b4bbdb6 --- /dev/null +++ b/pkg/generator/templates/backends/java/auth/[artifactId]-server/src/main/java/[groupIdSlashes]/[artifactIdSlashes]/domain/[nameLowerCase]/UserService.java.peb @@ -0,0 +1,179 @@ +package {{ groupId }}.{{ artifactId }}.domain.{{ nameLowerCase }}; + +import {{ groupId }}.{{ artifactId }}.domain.{{ nameLowerCase }}.{{ namePascalCase }}Event.*;{%for field in fields%}{%if field.isFieldRelational%} +import {{ groupId }}.{{ artifactId }}.domain.{{ field.fieldTypeLowerCase }}.{{ field.fieldTypePascalCase }}Service;{%endif%}{%endfor%} +import lombok.RequiredArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NonNull; +import lombok.With; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.ZoneId; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.stream.Collectors; +import java.util.stream.StreamSupport; + +import static java.util.Collections.singletonList; + +@Service +@Transactional +@RequiredArgsConstructor +public class {{ namePascalCase }}Service { + private final {{ namePascalCase }}Repository {{ nameCamelCase }}Repository;{%for field in fields%}{%if field.isFieldRelational%} + private final {{ field.fieldTypePascalCase }}Service {{ field.fieldTypeCamelCase }}Service;{%endif%}{%endfor%} + private final PasswordEncoder passwordEncoder; + private final ApplicationEventPublisher publisher; + + public Optional<{{ namePascalCase }}> findById(int id) { + return {{ nameCamelCase }}Repository.findById(id) + .map(this::toDomainObject); + } + + public Optional findByUsername(String username) { + return userRepository.findByUsername(username) + .map(this::toDomainObject); + } + + public Optional findUserDetailsByUsername(String username) { + return userRepository.findByUsername(username) + .map(it -> new UserDetailsImpl(it)); + } + + public List<{{ namePascalCase }}> findByIds(Iterable ids) { + return {{ nameCamelCase }}Repository.findByIds(ids) + .stream() + .map(this::toDomainObject) + .collect(Collectors.toList()); + } +{%for field in fields%}{%if field.isFieldRelational%} + public List<{{ namePascalCase }}> findBy{{ field.fieldNamePluralPascalCase }}(Iterable {{ field.fieldNamePluralCamelCase }}) { + return {{ nameCamelCase }}Repository.findBy{{ field.fieldNamePluralPascalCase }}({{ field.fieldNamePluralCamelCase }}) + .stream() + .map(this::toDomainObject) + .collect(Collectors.toList()); + } +{%endif%}{%endfor%}{%if hasRelations%} + public List<{{ namePascalCase }}> findAll() { + return findAll({{ namePascalCase }}FilterInput.builder().build()); + } +{%endif%} + public List<{{ namePascalCase }}> findAll({%if hasRelations%}{{ namePascalCase }}FilterInput filterInput{%endif%}) { {%for field in fields%}{%if field.isFieldRelational%} + if (filterInput.get{{ field.fieldNamePascalCase }}() != null) { + return {{ nameCamelCase }}Repository.findBy{{ field.fieldNamePluralPascalCase }}(singletonList(filterInput.{{ field.fieldNameCamelCase }})).stream() + .map(this::toDomainObject) + .collect(Collectors.toList()); + } +{%endif%}{%endfor%} + return StreamSupport.stream({{ nameCamelCase }}Repository.findAll().spliterator(), false) + .map(this::toDomainObject) + .collect(Collectors.toList()); + } + + public {{ namePascalCase }} register(RegisterInput input) { {%for field in fields%}{%if field.isFieldRelational%} + Objects.requireNonNull({{ field.fieldTypeCamelCase }}Service.findById(input.get{{ field.fieldNamePascalCase }}()).orElse(null)); +{%endif%}{%endfor%} + var {{ nameCamelCase }}Entity = {{ namePascalCase }}Entity.builder(){%for field in fields%}{%if field.isFieldRelational%} + .{{field.fieldNameCamelCase}}(input.get{{field.fieldNamePascalCase}}()){%endif%}{%endfor%} + .build(); + var {{ nameCamelCase }} = toDomainObject({{ nameCamelCase }}Repository.save({{ nameCamelCase }}Entity)); + publisher.publishEvent(new {{ namePluralPascalCase }}RegisteredEvent(this, singletonList({{ nameCamelCase }}))); + return {{ nameCamelCase }}; + } + + public Optional<{{ namePascalCase }}> update(int id, Update{{ namePascalCase }}Input input) { {%for field in fields%}{%if field.isFieldRelational%} + if (input.{{ field.fieldNameCamelCase }} != null) { + Objects.requireNonNull({{ field.fieldTypeCamelCase }}Service.findById(input.get{{ field.fieldNamePascalCase }}()).orElse(null)); + } +{%endif%}{%endfor%} + var {{ nameCamelCase }}Entity = {{ nameCamelCase }}Repository.findById(id); + if ({{ nameCamelCase }}Entity.isEmpty()) { + return Optional.empty(); + } + var saved{{ namePascalCase }}Entity = {{ nameCamelCase }}Repository.save( + {{ nameCamelCase }}Entity.get(){%for field in fields%}{%if field.isFieldRelational%} + .with{{field.fieldNamePascalCase}}(input.get{{field.fieldNamePascalCase}}() != null ? input.get{{field.fieldNamePascalCase}}() : {{ nameCamelCase }}Entity.get().get{{field.fieldNamePascalCase}}()){%endif%}{%endfor%}); + var {{ nameCamelCase }} = toDomainObject(saved{{ namePascalCase }}Entity); + publisher.publishEvent(new {{ namePluralPascalCase }}UpdatedEvent(this, singletonList({{ nameCamelCase }}))); + return Optional.of({{ nameCamelCase }}); + } + + public boolean deleteById(int id) { + var {{ nameCamelCase }} = {{ nameCamelCase }}Repository.findById(id) + .map(this::toDomainObject) + .orElse(null); + if ({{ nameCamelCase }} == null) { + return false; + } + {{ nameCamelCase }}Repository.deleteById(id); + publisher.publishEvent(new {{ namePluralPascalCase }}DeletedEvent(this, singletonList({{ nameCamelCase }}))); + return true; + } +{%for field in fields%}{%if field.isFieldRelational%} + public boolean deleteBy{{ field.fieldNamePluralPascalCase }}(List {{ field.fieldNamePluralCamelCase }}) { + var {{ nameCamelCase }}Entities = {{ nameCamelCase }}Repository.findBy{{ field.fieldNamePluralPascalCase }}({{ field.fieldNamePluralCamelCase }}); + if ({{ nameCamelCase }}Entities.isEmpty()) { + return false; + } + {{ nameCamelCase }}Repository.deleteAll({{ nameCamelCase }}Entities); + publisher.publishEvent(new {{ namePluralPascalCase }}DeletedEvent(this, {{ nameCamelCase }}Entities.stream() + .map(this::toDomainObject) + .collect(Collectors.toList()))); + return true; + } +{%endif%}{%endfor%} + private {{ namePascalCase }} toDomainObject({{ namePascalCase }}Entity entity) { + return {{ namePascalCase }}.builder() + .id(entity.getId()){%for field in fields%} + .{{field.fieldNameCamelCase}}({%if field.fieldNameCamelCase == "password"%}passwordEncoder.encode(entity.get{{field.fieldNamePascalCase}}()){%else%}entity.get{{field.fieldNamePascalCase}}(){%endif%}){%endfor%} + .createdAt(entity.getCreatedAt().atZone(ZoneId.systemDefault()).toOffsetDateTime()) + .updatedAt(entity.getUpdatedAt() != null + ? entity.getUpdatedAt().atZone(ZoneId.systemDefault()).toOffsetDateTime() + : null) + .build(); + } + + @Data + @With + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class LoginInput {{ "{" }}{%for field in fields%} + @NonNull + private {{ field.fieldJavaType }} {{ field.fieldNameCamelCase }};{%endfor%} + } + + @Data + @With + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class RegisterInput {{ "{" }}{%for field in fields%} + @NonNull + private {{ field.fieldJavaType }} {{ field.fieldNameCamelCase }};{%endfor%} + } + + @Data + @With + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class Update{{ namePascalCase }}Input {{ "{" }}{%for field in fields%}{%if field.fieldNameCamelCase != "username"%} + private {{ field.fieldJavaType }} {{ field.fieldNameCamelCase }};{%endif%}{%endfor%} + {{ "}" }}{%if hasRelations%} + + @Data + @With + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class {{ namePascalCase }}FilterInput {{ "{" }}{%for field in fields%}{%if field.isFieldRelational%} + private {{ field.fieldJavaType }} {{ field.fieldNameCamelCase }};{%endif%}{%endfor%} + {{ "}" }}{%endif%} +} diff --git a/pkg/generator/templates/backends/java/auth/[artifactId]-server/src/main/resources/db/migration/V[nextMigrationPrefix]__create_[nameSnakeCase].sql.peb b/pkg/generator/templates/backends/java/auth/[artifactId]-server/src/main/resources/db/migration/V[nextMigrationPrefix]__create_[nameSnakeCase].sql.peb new file mode 100644 index 0000000..0aed9de --- /dev/null +++ b/pkg/generator/templates/backends/java/auth/[artifactId]-server/src/main/resources/db/migration/V[nextMigrationPrefix]__create_[nameSnakeCase].sql.peb @@ -0,0 +1,7 @@ +CREATE TABLE users ( + id BIGSERIAL PRIMARY KEY{%for field in fields%}, + {%if field.fieldNameCamelCase != "passwordHash" %}{{ field.fieldNameSnakeCase }}{%else%}password_hash{%endif%} {{ field.fieldDatabaseDefinitionType }}{%endfor%}, + created_at TIMESTAMP, + updated_at TIMESTAMP, + UNIQUE(username) +); diff --git a/pkg/generator/templates/backends/java/auth/[artifactId]-server/src/main/resources/schema/[namePluralCamelCase].graphqls.peb b/pkg/generator/templates/backends/java/auth/[artifactId]-server/src/main/resources/schema/[namePluralCamelCase].graphqls.peb new file mode 100644 index 0000000..46d57cf --- /dev/null +++ b/pkg/generator/templates/backends/java/auth/[artifactId]-server/src/main/resources/schema/[namePluralCamelCase].graphqls.peb @@ -0,0 +1,34 @@ +extend type Query { + currentUser: User + user(id: ID!): User + users{%if hasRelations%}(filter: UserFilter){%endif%}: [User!]! +} + +extend type Mutation { + login(input: LoginInput!): User + logout: Boolean! + register(input: RegisterInput!): User! + updateUser(id: ID!, input: UpdateUserInput!): User + deleteUser(id: ID!): Boolean! +} + +type User { + id: ID!{%for field in fields%}{%if field.fieldNameCamelCase != "password" %} + {{ field.fieldNameCamelCase }}: {{ field.graphQLFieldType }}{%endif%}{%endfor%}{%for field in fields%}{%if field.isFieldRelational%} + {{ field.fieldTypeCamelCase }}: {{ field.fieldTypePascalCase }}{%endif%}{%endfor%} + createdAt: Date! + updatedAt: Date +} + +input LoginInput { + username: String! + password: String! +} + +input RegisterInput { {%for field in fields%} + {{ field.fieldNameCamelCase }}: {{ field.graphQLFieldType }}{%endfor%} +} + +input UpdateUserInput { {%for field in fields%}{%if field.fieldNameCamelCase != "username"%} + {{ field.fieldNameCamelCase }}: {{ field.graphQLFieldType }}{%endif%}{%endfor%} +} diff --git a/pkg/generator/templates/backends/java/auth/[artifactId]-server/src/test/java/[groupIdSlashes]/[artifactIdSlashes]/application/FakeSecurityConfig.java.peb b/pkg/generator/templates/backends/java/auth/[artifactId]-server/src/test/java/[groupIdSlashes]/[artifactIdSlashes]/application/FakeSecurityConfig.java.peb new file mode 100644 index 0000000..526f22f --- /dev/null +++ b/pkg/generator/templates/backends/java/auth/[artifactId]-server/src/test/java/[groupIdSlashes]/[artifactIdSlashes]/application/FakeSecurityConfig.java.peb @@ -0,0 +1,19 @@ +package {{ groupId }}.{{ artifactId }}.application; + +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.security.authentication.AuthenticationProvider; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +@TestConfiguration +public class FakeSecurityConfig { + @Bean + AuthenticationProvider authenticationProvider() { + var authenticationProvider = mock(AuthenticationProvider.class); + when(authenticationProvider.authenticate(any())).thenReturn(mock()); + return authenticationProvider; + } +} diff --git a/pkg/generator/templates/backends/java/auth/[artifactId]-server/src/test/java/[groupIdSlashes]/[artifactIdSlashes]/application/ResolverTestContext.java.peb b/pkg/generator/templates/backends/java/auth/[artifactId]-server/src/test/java/[groupIdSlashes]/[artifactIdSlashes]/application/ResolverTestContext.java.peb new file mode 100644 index 0000000..bf07484 --- /dev/null +++ b/pkg/generator/templates/backends/java/auth/[artifactId]-server/src/test/java/[groupIdSlashes]/[artifactIdSlashes]/application/ResolverTestContext.java.peb @@ -0,0 +1,59 @@ +package {{ groupId }}.{{ artifactId }}.application; + +import {{ groupId }}.{{ artifactId }}.application.security.SecurityConfig; +import org.junit.jupiter.api.BeforeEach; +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Import; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.core.context.SecurityContextHolder; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +@Import(ResolverTestContext.Config.class) +@EnableMethodSecurity(prePostEnabled = true) +abstract class ResolverTestContext { + @MockBean + protected AuthenticationManager authenticationManager; + + @MockBean + protected SecurityContext securityContext; + + protected Authentication authenticationMock; + + @TestConfiguration + static class Config { + @Bean + ThreadPoolTaskExecutor executor() { + var poolExecutor = new ThreadPoolTaskExecutor(); + poolExecutor.setTaskDecorator(new SecurityConfig.ContextCopyingDecorator()); + poolExecutor.initialize(); + return poolExecutor; + } + } + + @BeforeEach + void init() { + authenticationMock = mock(); + when(authenticationMock.isAuthenticated()) + .thenReturn(true); + + when(authenticationManager.authenticate(any())) + .thenReturn(authenticationMock); + + when(authenticationMock.getName()) + .thenReturn("Some Username"); + + when(securityContext.getAuthentication()) + .thenReturn(authenticationMock); + + SecurityContextHolder.setContext(securityContext); + } +} diff --git a/pkg/generator/templates/backends/java/auth/[artifactId]-server/src/test/java/[groupIdSlashes]/[artifactIdSlashes]/application/graphql/user/UserResolverTests.java.peb b/pkg/generator/templates/backends/java/auth/[artifactId]-server/src/test/java/[groupIdSlashes]/[artifactIdSlashes]/application/graphql/user/UserResolverTests.java.peb new file mode 100644 index 0000000..5eaf524 --- /dev/null +++ b/pkg/generator/templates/backends/java/auth/[artifactId]-server/src/test/java/[groupIdSlashes]/[artifactIdSlashes]/application/graphql/user/UserResolverTests.java.peb @@ -0,0 +1,157 @@ +package {{ groupId }}.{{ artifactId }}.application.graphql.{{ nameLowerCase }}; + +import com.netflix.graphql.dgs.DgsQueryExecutor; +import com.netflix.graphql.dgs.autoconfig.DgsAutoConfiguration; +import com.netflix.graphql.dgs.autoconfig.DgsExtendedScalarsAutoConfiguration;{%for field in fields%}{%if field.isFieldRelational%} +import {{ groupId }}.{{ artifactId }}.application.graphql.{{ field.fieldTypeLowerCase }}.{{ field.fieldTypePascalCase }}DataLoader; +import {{ groupId }}.{{ artifactId }}.application.graphql.{{ field.fieldTypeLowerCase }}.{{ field.fieldTypePascalCase }}Resolver; +import static {{ groupId }}.{{ artifactId }}.domain.{{ field.fieldTypeLowerCase }}.{{ field.fieldTypePascalCase }}Fixtures.{{ field.fieldTypeScreamingSnakeCase }}_FIXTURE_WITH_ID_1; +import {{ groupId }}.{{ artifactId }}.domain.{{ field.fieldTypeLowerCase }}.{{ field.fieldTypePascalCase }}Service;{%endif%}{%endfor%} +import static {{ groupId }}.{{ artifactId }}.domain.{{ nameLowerCase }}.{{ namePascalCase }}Fixtures.REGISTER_{{ nameScreamingSnakeCase }}_FIXTURE_1; +import static {{ groupId }}.{{ artifactId }}.domain.{{ nameLowerCase }}.{{ namePascalCase }}Fixtures.{{ nameScreamingSnakeCase }}_FIXTURE_WITH_ID_1; +import static {{ groupId }}.{{ artifactId }}.domain.{{ nameLowerCase }}.{{ namePascalCase }}Fixtures.UPDATE_{{ nameScreamingSnakeCase }}_FIXTURE_1; +import {{ groupId }}.{{ artifactId }}.domain.{{ nameLowerCase }}.{{ namePascalCase }}Service; +import {{ groupId }}.{{ artifactId }}.application.FakeSecurityConfig; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; + +import java.util.List; +import java.util.Map; +import java.util.Optional;{%if hasRelations%} +import java.util.Set;{%endif%} + +import static java.util.Collections.singletonList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@SpringBootTest( + classes = { + DgsAutoConfiguration.class, + DgsExtendedScalarsAutoConfiguration.class,{%for field in fields%}{%if field.isFieldRelational%} + {{ field.fieldTypePascalCase }}Resolver.class, + {{ field.fieldTypePascalCase }}DataLoader.class,{%endif%}{%endfor%} + {{ namePascalCase }}Resolver.class, + {{ namePascalCase }}DataLoader.class, + } +) +@Import(FakeSecurityConfig.class) +class {{ namePascalCase }}ResolverTests { + + @Autowired + private DgsQueryExecutor dgsQueryExecutor; +{%for field in fields%}{%if field.isFieldRelational%} + @MockBean + private {{ field.fieldTypePascalCase }}Service {{ field.fieldTypeCamelCase }}Service; +{%endif%}{%endfor%} + @MockBean + private {{ namePascalCase }}Service {{ nameCamelCase }}Service; + + @Test + void {{ namePluralCamelCase }}() { + when({{ nameCamelCase }}Service.findAll()) + .thenReturn(singletonList({{ nameScreamingSnakeCase }}_FIXTURE_WITH_ID_1)); + + var ids = dgsQueryExecutor.>executeAndExtractJsonPath( + "{ {{ namePluralCamelCase }} { id } }", + "data.{{ namePluralCamelCase }}[*].id" + ); + + assertThat(ids).contains("" + {{ nameScreamingSnakeCase }}_FIXTURE_WITH_ID_1.getId()); + } + + @Test + void register_delegates_the_call_to_the_services_register() { + when({{ nameCamelCase }}Service.register(any())) + .thenReturn({{ nameScreamingSnakeCase }}_FIXTURE_WITH_ID_1); + + var registerInput = Map.of({%for field in fields%} + "{{ field.fieldNameCamelCase }}", {{ field.fieldKotlinTestDummyValue }}{%if field.isNotLast%},{%endif%}{%endfor%} + ); + + var result = dgsQueryExecutor.executeAndExtractJsonPath( + "mutation register($input: RegisterInput!) { register(input: $input) { id } }", + "data.register.id", + Map.of("input", registerInput) + ); + + assertThat(result).isEqualTo("" + {{ nameScreamingSnakeCase }}_FIXTURE_WITH_ID_1.getId()); + + verify({{ nameCamelCase }}Service).register(REGISTER_{{ nameScreamingSnakeCase }}_FIXTURE_1); + } + + @Test + void update{{ namePascalCase }}_delegates_the_call_to_the_services_update() { + when({{ nameCamelCase }}Service.update(anyInt(), any())) + .thenReturn(Optional.of({{ nameScreamingSnakeCase }}_FIXTURE_WITH_ID_1)); + + var update{{ namePascalCase }}Input = Map.of({%for field in fields%} + "{{ field.fieldNameCamelCase }}", {{ field.fieldKotlinTestDummyValue }}{%if field.isNotLast%},{%endif%}{%endfor%} + ); + + var result = dgsQueryExecutor.executeAndExtractJsonPath( + "mutation update{{ namePascalCase }}($id: ID!, $input: Update{{ namePascalCase }}Input!) { update{{ namePascalCase }}(id: $id, input: $input) { id } }", + "data.update{{ namePascalCase }}.id", + Map.of("id", {{ nameScreamingSnakeCase }}_FIXTURE_WITH_ID_1.getId(), "input", update{{ namePascalCase }}Input) + ); + + assertThat(result).isEqualTo("" + {{ nameScreamingSnakeCase }}_FIXTURE_WITH_ID_1.getId()); + + verify({{ nameCamelCase }}Service).update({{ nameScreamingSnakeCase }}_FIXTURE_WITH_ID_1.getId(), UPDATE_{{ nameScreamingSnakeCase }}_FIXTURE_1); + } + + @Test + void delete{{ namePascalCase }}() { + when({{ nameCamelCase }}Service.deleteById(anyInt())) + .thenReturn(true); + + var result = dgsQueryExecutor.executeAndExtractJsonPath( + "mutation delete{{ namePascalCase }}($id: ID!) { delete{{ namePascalCase }}(id: $id) }", + "data.delete{{ namePascalCase }}", + Map.of("id", 1) + ); + + assertThat(result).isEqualTo(true); + + verify({{ nameCamelCase }}Service).deleteById(1); + } +{%for field in fields%}{%if field.isFieldRelational%} + @Test + void {{ namePluralCamelCase }}_{{ field.fieldTypeCamelCase }}() { + when({{ nameCamelCase }}Service.findAll()) + .thenReturn(singletonList({{ nameScreamingSnakeCase }}_FIXTURE_WITH_ID_1)); + + when({{ field.fieldTypeCamelCase }}Service.findByIds(Set.of({{ nameScreamingSnakeCase }}_FIXTURE_WITH_ID_1.get{{ field.fieldNamePascalCase }}()))) + .thenReturn(singletonList({{ field.fieldTypeScreamingSnakeCase }}_FIXTURE_WITH_ID_1 + .withId({{ nameScreamingSnakeCase }}_FIXTURE_WITH_ID_1.get{{ field.fieldNamePascalCase }}()))); + + var ids = dgsQueryExecutor.>executeAndExtractJsonPath( + "{ {{ namePluralCamelCase }} { {{ field.fieldTypeCamelCase }} { id } } }", + "data.{{ namePluralCamelCase }}[*].{{ field.fieldTypeCamelCase }}.id" + ); + + assertThat(ids).contains({{ nameScreamingSnakeCase }}_FIXTURE_WITH_ID_1.get{{ field.fieldNamePascalCase }}().toString()); + } + + @Test + void {{ field.fieldTypePluralCamelCase }}_{{ namePluralCamelCase }}() { + when({{ field.fieldTypeCamelCase }}Service.findAll()) + .thenReturn(singletonList({{ field.fieldTypeScreamingSnakeCase }}_FIXTURE_WITH_ID_1 + .withId({{ nameScreamingSnakeCase }}_FIXTURE_WITH_ID_1.get{{ field.fieldNamePascalCase }}()))); + + when({{ nameCamelCase }}Service.findBy{{ field.fieldNamePluralPascalCase }}(Set.of({{ nameScreamingSnakeCase }}_FIXTURE_WITH_ID_1.get{{ field.fieldNamePascalCase }}()))) + .thenReturn(singletonList({{ nameScreamingSnakeCase }}_FIXTURE_WITH_ID_1)); + + var ids = dgsQueryExecutor.>executeAndExtractJsonPath( + "{ {{ field.fieldTypePluralCamelCase }} { {{ namePluralCamelCase }} { id } } }", + "data.{{ field.fieldTypePluralCamelCase }}[*].{{ namePluralCamelCase }}[*].id" + ); + + assertThat(ids).contains("" + {{ nameScreamingSnakeCase }}_FIXTURE_WITH_ID_1.getId()); + } +{%endif%}{%endfor%} +} diff --git a/pkg/generator/templates/backends/java/auth/[artifactId]-server/src/test/java/[groupIdSlashes]/[artifactIdSlashes]/domain/[nameLowerCase]/UserFixtures.java.peb b/pkg/generator/templates/backends/java/auth/[artifactId]-server/src/test/java/[groupIdSlashes]/[artifactIdSlashes]/domain/[nameLowerCase]/UserFixtures.java.peb new file mode 100644 index 0000000..419ce1b --- /dev/null +++ b/pkg/generator/templates/backends/java/auth/[artifactId]-server/src/test/java/[groupIdSlashes]/[artifactIdSlashes]/domain/[nameLowerCase]/UserFixtures.java.peb @@ -0,0 +1,42 @@ +package {{ groupId }}.{{ artifactId }}.domain.user; + +import java.time.LocalDateTime; +import java.time.OffsetDateTime; +import java.time.ZoneId; + +public class {{ namePascalCase }}Fixtures { + + private static final LocalDateTime LOCAL_DATE_TIME_FIXTURE = LocalDateTime.now(); + private static final OffsetDateTime OFFSET_DATE_TIME_FIXTURE = LOCAL_DATE_TIME_FIXTURE + .atZone(ZoneId.systemDefault()) + .toOffsetDateTime(); + + public static final {{ namePascalCase }} {{ nameScreamingSnakeCase }}_FIXTURE_WITH_ID_1 = + {{ namePascalCase }}.builder() + .id(1){%for field in fields%} + .{{ field.fieldNameCamelCase }}({{ field.fieldKotlinTestDummyValue }}){%endfor%} + .createdAt(OFFSET_DATE_TIME_FIXTURE) + .build(); + + public static final {{ namePascalCase }}Service.RegisterInput REGISTER_{{ nameScreamingSnakeCase }}_FIXTURE_1 = + {{ namePascalCase }}Service.RegisterInput.builder(){%for field in fields%} + .{{ field.fieldNameCamelCase }}({{ field.fieldKotlinTestDummyValue }}){%endfor%} + .build(); + + public static final {{ namePascalCase }}Service.Update{{ namePascalCase }}Input UPDATE_{{ nameScreamingSnakeCase }}_FIXTURE_1 = + {{ namePascalCase }}Service.Update{{ namePascalCase }}Input.builder(){%for field in fields%}{%if field.fieldNameCamelCase != "username"%} + .{{ field.fieldNameCamelCase }}({{ field.fieldKotlinTestDummyValue }}){%endif%}{%endfor%} + .build(); + + public static final {{ namePascalCase }}Entity {{ nameScreamingSnakeCase }}_ENTITY_FIXTURE_1 = + {{ namePascalCase }}Entity.builder() + .id(null){%for field in fields%} + .{{ field.fieldNameCamelCase }}({{ field.fieldKotlinTestDummyValue }}){%endfor%} + .build(); + + public static final {{ namePascalCase }}Entity {{ nameScreamingSnakeCase }}_ENTITY_FIXTURE_WITH_ID_1 = + {{ nameScreamingSnakeCase }}_ENTITY_FIXTURE_1 + .withId(1) + .withCreatedAt(LOCAL_DATE_TIME_FIXTURE); + +} diff --git a/pkg/generator/templates/backends/java/auth/[artifactId]-server/src/test/java/[groupIdSlashes]/[artifactIdSlashes]/domain/[nameLowerCase]/UserIntegrationTests.java.peb b/pkg/generator/templates/backends/java/auth/[artifactId]-server/src/test/java/[groupIdSlashes]/[artifactIdSlashes]/domain/[nameLowerCase]/UserIntegrationTests.java.peb new file mode 100644 index 0000000..040816a --- /dev/null +++ b/pkg/generator/templates/backends/java/auth/[artifactId]-server/src/test/java/[groupIdSlashes]/[artifactIdSlashes]/domain/[nameLowerCase]/UserIntegrationTests.java.peb @@ -0,0 +1,66 @@ +package {{ groupId }}.{{ artifactId }}.domain.user; + +import {{ groupId }}.{{ artifactId }}.IntegrationTestContext; +import static {{ groupId }}.{{ artifactId }}.domain.{{ nameLowerCase }}.{{ namePascalCase }}Fixtures.REGISTER_{{ nameScreamingSnakeCase }}_FIXTURE_1; +import static {{ groupId }}.{{ artifactId }}.domain.{{ nameLowerCase }}.{{ namePascalCase }}Fixtures.UPDATE_{{ nameScreamingSnakeCase }}_FIXTURE_1;{%for field in fields%}{%if field.isFieldRelational%} +import static {{ groupId }}.{{ artifactId }}.domain.{{ field.fieldTypeLowerCase }}.{{ field.fieldTypePascalCase }}Fixtures.{{ field.fieldTypeScreamingSnakeCase }}_ENTITY_FIXTURE_1; +import {{ groupId }}.{{ artifactId }}.domain.{{ field.fieldTypeLowerCase }}.{{ field.fieldTypePascalCase }}Repository; +import {{ groupId }}.{{ artifactId }}.domain.{{ field.fieldTypeLowerCase }}.{{ field.fieldTypePascalCase }}Service;{%endif%}{%endfor%} +import static org.assertj.core.api.Assertions.assertThat; +import org.springframework.beans.factory.annotation.Autowired; +import org.junit.jupiter.api.Test;{%if hasRelations%} +import static org.junit.jupiter.api.Assertions.assertThrows;{%endif%} +import static java.util.Collections.singletonList; + +class {{ namePascalCase }}IntegrationTests extends IntegrationTestContext { + + @Autowired + private {{ namePascalCase }}Service {{ nameCamelCase }}Service; +{%for field in fields%}{%if field.isFieldRelational%} + @Autowired + private {{ field.fieldTypePascalCase }}Service {{ field.fieldTypeCamelCase }}Service; + + @Autowired + private {{ field.fieldTypePascalCase }}Repository {{ field.fieldTypeCamelCase }}Repository; +{%endif%}{%endfor%}{%if hasRelations%} + @Test + void {{ namePascalCase }}s_cannot_be_registered_if_related_entities_do_not_exist() { + assertThrows(RuntimeException.class, () -> + {{ nameCamelCase }}Service.register(REGISTER_{{ nameScreamingSnakeCase }}_FIXTURE_1)); + assertThat({{ nameCamelCase }}Service.findAll()).isEmpty(); + } +{%endif%} + @Test + void {{ namePluralPascalCase }}_can_be_registered_updated_fetched_and_deleted() { {%for field in fields%}{%if field.isFieldRelational%} + var {{ field.fieldTypeCamelCase }} = {{ field.fieldTypeCamelCase }}Repository.save({{ field.fieldTypeScreamingSnakeCase }}_ENTITY_FIXTURE_1); +{%endif%}{%endfor%} + var {{ nameCamelCase }} = {{ nameCamelCase }}Service.register(REGISTER_{{ nameScreamingSnakeCase }}_FIXTURE_1{%if hasRelations%}{%for field in fields%}{%if field.isFieldRelational%} + .with{{ field.fieldNamePascalCase }}({{ field.fieldTypeCamelCase }}.getId()){%endif%}{%endfor%}{%endif%}); + {{ nameCamelCase }} = {{ nameCamelCase }}Service.update({{ nameCamelCase }}.getId(), UPDATE_{{ nameScreamingSnakeCase }}_FIXTURE_1{%if hasRelations%}{%for field in fields%}{%if field.isFieldRelational%} + .with{{ field.fieldNamePascalCase }}({{ field.fieldTypeCamelCase }}.getId()){%endif%}{%endfor%}{%endif%}).get(); + + assertThat({{ nameCamelCase }}Service.findAll()).isNotEmpty(); + assertThat({{ nameCamelCase }}Service.findById({{ nameCamelCase }}.getId())).isNotNull(); + assertThat({{ nameCamelCase }}Service.findByIds(singletonList({{ nameCamelCase }}.getId()))).isNotEmpty(); +{%for field in fields%}{%if field.isFieldRelational%} + assertThat({{ nameCamelCase }}Service.findAll(new {{ namePascalCase }}Service.{{ namePascalCase }}FilterInput({{ field.fieldTypeCamelCase }}.getId()))).isNotEmpty(); + assertThat({{ nameCamelCase }}Service.findBy{{ field.fieldNamePluralPascalCase }}(singletonList({{ field.fieldTypeCamelCase }}.getId()))).isNotEmpty(); +{%endif%}{%endfor%} + {{ nameCamelCase }}Service.deleteById({{ nameCamelCase }}.getId()); + + assertThat({{ nameCamelCase }}Service.findAll()).isEmpty(); + } +{%for field in fields%}{%if field.isFieldRelational%} + @Test + void {{ namePascalCase }}s_are_deleted_when_related_{{ field.fieldTypePluralPascalCase }}_are_deleted() { +{%for field in fields%}{%if field.isFieldRelational%} var {{ field.fieldTypeCamelCase }} = {{ field.fieldTypeCamelCase }}Repository.save({{ field.fieldTypeScreamingSnakeCase }}_ENTITY_FIXTURE_1); +{%endif%}{%endfor%} + var {{ nameCamelCase }} = {{ nameCamelCase }}Service.register(REGISTER_{{ nameScreamingSnakeCase }}_FIXTURE_1{%for field in fields%}{%if field.isFieldRelational%} + .with{{ field.fieldNamePascalCase }}({{ field.fieldTypeCamelCase }}.getId()){%endif%}{%endfor%}); + + {{ field.fieldTypeCamelCase }}Service.deleteById({{ nameCamelCase }}.get{{ field.fieldNamePascalCase }}()); + + assertThat({{ nameCamelCase }}Service.findAll()).isEmpty(); + } +{%endif%}{%endfor%} +} diff --git a/pkg/generator/templates/backends/java/auth/[artifactId]-server/src/test/java/[groupIdSlashes]/[artifactIdSlashes]/domain/[nameLowerCase]/UserListenerUnitTests.java.peb b/pkg/generator/templates/backends/java/auth/[artifactId]-server/src/test/java/[groupIdSlashes]/[artifactIdSlashes]/domain/[nameLowerCase]/UserListenerUnitTests.java.peb new file mode 100644 index 0000000..ba249b2 --- /dev/null +++ b/pkg/generator/templates/backends/java/auth/[artifactId]-server/src/test/java/[groupIdSlashes]/[artifactIdSlashes]/domain/[nameLowerCase]/UserListenerUnitTests.java.peb @@ -0,0 +1,29 @@ +{%if hasRelations%}package {{ groupId }}.{{ artifactId }}.domain.{{ nameLowerCase }}; + +{%for field in fields%}{%if field.isFieldRelational%}import {{ groupId }}.{{ artifactId }}.domain.{{ field.fieldTypeLowerCase }}.{{ field.fieldTypePascalCase }}Event.{{ field.fieldTypePluralPascalCase }}DeletedEvent; +{%endif%}{%endfor%}import static org.mockito.Mockito.verify; +import org.junit.jupiter.api.Test; +{%for field in fields%}{%if field.isFieldRelational%}import static {{ groupId }}.{{ artifactId }}.domain.{{ field.fieldTypeLowerCase }}.{{ field.fieldTypePascalCase }}Fixtures.{{ field.fieldTypeScreamingSnakeCase }}_FIXTURE_WITH_ID_1; +{%endif%}{%endfor%}import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks;{%if hasRelations%} +import org.mockito.Mock;{%endif%} +import org.mockito.junit.jupiter.MockitoExtension; +import static java.util.Collections.singletonList; + +@ExtendWith(MockitoExtension.class) +class {{ namePascalCase }}ListenerUnitTests{%if hasRelations%}{ + + @Mock + private {{ namePascalCase }}Service {{ nameCamelCase }}Service; + + @InjectMocks + private {{ namePascalCase }}Listener {{ nameCamelCase }}Listener; +{%for field in fields%}{%if field.isFieldRelational%} + @Test + void when_{{ field.fieldTypePluralPascalCase }}DeletedEvent_is_triggered_related_{{ namePluralPascalCase }}_are_deleted() { + {{ nameCamelCase }}Listener.onEvent(new {{ field.fieldTypePluralPascalCase }}DeletedEvent(this, singletonList({{ field.fieldTypeScreamingSnakeCase }}_FIXTURE_WITH_ID_1))); + + verify({{ nameCamelCase }}Service).deleteBy{{ field.fieldNamePluralPascalCase }}(singletonList({{ field.fieldTypeScreamingSnakeCase }}_FIXTURE_WITH_ID_1.getId())); + } +{%endif%}{%endfor%} +}{%endif%}{%endif%} diff --git a/pkg/generator/templates/backends/java/auth/[artifactId]-server/src/test/java/[groupIdSlashes]/[artifactIdSlashes]/domain/[nameLowerCase]/UserServiceUnitTests.java.peb b/pkg/generator/templates/backends/java/auth/[artifactId]-server/src/test/java/[groupIdSlashes]/[artifactIdSlashes]/domain/[nameLowerCase]/UserServiceUnitTests.java.peb new file mode 100644 index 0000000..0de904a --- /dev/null +++ b/pkg/generator/templates/backends/java/auth/[artifactId]-server/src/test/java/[groupIdSlashes]/[artifactIdSlashes]/domain/[nameLowerCase]/UserServiceUnitTests.java.peb @@ -0,0 +1,270 @@ +package {{ groupId }}.{{ artifactId }}.domain.{{ nameLowerCase }}; +{%for field in fields%}{%if field.isFieldRelational%} +import {{ groupId }}.{{ artifactId }}.domain.{{ field.fieldTypeLowerCase }}.{{ field.fieldTypePascalCase }}Service;{%endif%}{%endfor%} +import static {{ groupId }}.{{ artifactId }}.domain.{{ nameLowerCase }}.{{ namePascalCase }}Fixtures.{{ nameScreamingSnakeCase }}_ENTITY_FIXTURE_1; +import static {{ groupId }}.{{ artifactId }}.domain.{{ nameLowerCase }}.{{ namePascalCase }}Fixtures.{{ nameScreamingSnakeCase }}_ENTITY_FIXTURE_WITH_ID_1; +import static {{ groupId }}.{{ artifactId }}.domain.{{ nameLowerCase }}.{{ namePascalCase }}Fixtures.REGISTER_{{ nameScreamingSnakeCase }}_FIXTURE_1; +import static {{ groupId }}.{{ artifactId }}.domain.{{ nameLowerCase }}.{{ namePascalCase }}Fixtures.UPDATE_{{ nameScreamingSnakeCase }}_FIXTURE_1; +import static {{ groupId }}.{{ artifactId }}.domain.{{ nameLowerCase }}.{{ namePascalCase }}Fixtures.{{ nameScreamingSnakeCase }}_FIXTURE_WITH_ID_1;{%for field in fields%}{%if field.isFieldRelational%} +import static {{ groupId }}.{{ artifactId }}.domain.{{ field.fieldTypeLowerCase }}.{{ field.fieldTypePascalCase }}Fixtures.{{ field.fieldTypeScreamingSnakeCase }}_FIXTURE_WITH_ID_1; +import static {{ groupId }}.{{ artifactId }}.domain.{{ field.fieldTypeLowerCase }}.{{ field.fieldTypePascalCase }}Fixtures.{{ field.fieldTypeScreamingSnakeCase }}_ENTITY_FIXTURE_WITH_ID_1;{%endif%}{%endfor%} + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.security.crypto.password.PasswordEncoder; + +import java.util.Optional; +{%if hasRelations%} +import static java.util.Collections.emptyList;{%endif%} +import static java.util.Collections.singletonList; +import static org.assertj.core.api.Assertions.assertThat;{%if hasRelations%} +import static org.junit.jupiter.api.Assertions.assertThrows;{%endif%} +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +class {{ namePascalCase }}ServiceUnitTests { + + @Mock + private ApplicationEventPublisher publisher; + + @Mock + private {{ namePascalCase }}Repository {{ nameCamelCase }}Repository; +{%for field in fields%}{%if field.isFieldRelational%} + @Mock + private {{ field.fieldTypePascalCase }}Service {{ field.fieldTypeCamelCase }}Service; +{%endif%}{%endfor%} + @Mock + private PasswordEncoder passwordEncoder; + + @InjectMocks + private {{ namePascalCase }}Service {{ nameCamelCase }}Service; + + @Test + void findById_returns_the_mapped_value_of_findById_in_{{ namePascalCase }}Repository_if_it_is_non_empty() { + when({{ nameCamelCase }}Repository.findById(1)) + .thenReturn(Optional.of({{ nameScreamingSnakeCase }}_ENTITY_FIXTURE_WITH_ID_1)); + + var actual = {{ nameCamelCase }}Service.findById(1); + + assertThat(actual).isEqualTo(Optional.of({{ nameScreamingSnakeCase }}_FIXTURE_WITH_ID_1)); + } + + @Test + void findById_returns_null_if_findById_in_{{ namePascalCase }}Repository_yields_an_empty_Optional() { + when({{ nameCamelCase }}Repository.findById(1)) + .thenReturn(Optional.empty()); + + var actual = {{ nameCamelCase }}Service.findById(1); + + assertThat(actual).isEmpty(); + + verify(publisher, times(0)) + .publishEvent(any()); + } + + @Test + void findByUsername_returns_the_mapped_value_of_findByUsername_in_UserRepository_if_it_is_nonempty() { + when(userRepository.findByUsername("username")) + .thenReturn(Optional.of(USER_ENTITY_FIXTURE_WITH_ID_1)); + + var actual = userService.findByUsername("username"); + + assertThat(actual).isEqualTo(Optional.of(USER_FIXTURE_WITH_ID_1)); + } + + @Test + void findByUsername_returns_null_if_findByUsername_in_UserRepository_yields_a_value() { + when(userRepository.findByUsername("username")) + .thenReturn(Optional.empty()); + + var actual = userService.findByUsername("username"); + + assertThat(actual).isEmpty(); + + verify(publisher, times(0)) + .publishEvent(any()); + } + + @Test + void findUserDetailsByUsername_returns_the_mapped_value_of_findByUsername_in_UserRepository_if_it_is_nonempty() { + when(userRepository.findByUsername("username")) + .thenReturn(Optional.of(USER_ENTITY_FIXTURE_WITH_ID_1)); + + var actual = userService.findUserDetailsByUsername("username"); + + assertThat(actual.get().getUsername()).isEqualTo(USER_FIXTURE_WITH_ID_1.getUsername()); + assertThat(actual.get().getPassword()).isEqualTo(USER_FIXTURE_WITH_ID_1.getPassword()); + } + + @Test + void findUserDetailsByUsername_returns_null_if_findByUsername_in_UserRepository_yields_a_value() { + when(userRepository.findByUsername("username")) + .thenReturn(Optional.empty()); + + var actual = userService.findUserDetailsByUsername("username"); + + assertThat(actual).isEmpty(); + + verify(publisher, times(0)) + .publishEvent(any()); + } + + @Test + void findByIds_returns_the_mapped_values_of_findByIds_in_{{ namePascalCase }}Repository() { + when({{ nameCamelCase }}Repository.findByIds(singletonList(1))) + .thenReturn(singletonList({{ nameScreamingSnakeCase }}_ENTITY_FIXTURE_WITH_ID_1)); + + var actual = {{ nameCamelCase }}Service.findByIds(singletonList(1)); + + assertThat(actual).isEqualTo(singletonList({{ nameScreamingSnakeCase }}_FIXTURE_WITH_ID_1)); + } +{%for field in fields%}{%if field.isFieldRelational%} + @Test + void findBy{{field.fieldNamePluralPascalCase}}_returns_the_contents_of_the_corresponding_repository_call() { + when({{ nameCamelCase }}Repository.findBy{{ field.fieldNamePluralPascalCase }}(singletonList(1))) + .thenReturn(singletonList({{ nameScreamingSnakeCase }}_ENTITY_FIXTURE_WITH_ID_1)); + + var actual = {{ nameCamelCase }}Service.findBy{{ field.fieldNamePluralPascalCase }}(singletonList(1)); + + assertThat(actual).isEqualTo(actual); + } +{%endif%}{%endfor%} + @Test + void findAll_returns_the_values_of_findAll_in_{{ namePascalCase }}Repository() { + when({{ nameCamelCase }}Repository.findAll()) + .thenReturn(singletonList({{ nameScreamingSnakeCase }}_ENTITY_FIXTURE_WITH_ID_1)); + + var actual = {{ nameCamelCase }}Service.findAll(); + + assertThat(actual).containsExactly({{ nameScreamingSnakeCase }}_FIXTURE_WITH_ID_1); + } +{%if hasRelations%} + @Test + void findAll_returns_the_values_of_findAll_in_CommentRepository_when_filter_is_empty() { + when({{ nameCamelCase }}Repository.findAll()) + .thenReturn(singletonList({{ nameScreamingSnakeCase }}_ENTITY_FIXTURE_WITH_ID_1)); + + var actual = {{ nameCamelCase }}Service.findAll({{ namePascalCase }}Service.{{ namePascalCase }}FilterInput.builder().build()); + + assertThat(actual).containsExactly({{ nameScreamingSnakeCase }}_FIXTURE_WITH_ID_1); + } +{%endif%}{%for field in fields%}{%if field.isFieldRelational%} + @Test + void findAll_delegates_to_findBy{{field.fieldNamePascalCase}}_when_filter_only_contains_{{field.fieldNameCamelCase}}() { + when({{ nameCamelCase }}Repository.findBy{{field.fieldNamePluralPascalCase}}(singletonList(1))) + .thenReturn(singletonList({{ nameScreamingSnakeCase }}_ENTITY_FIXTURE_WITH_ID_1)); + + var actual = {{ nameCamelCase }}Service.findAll({{ namePascalCase }}Service.{{ namePascalCase }}FilterInput.builder().{{field.fieldNameCamelCase}}(1).build()); + + assertThat(actual).containsExactly({{ nameScreamingSnakeCase }}_FIXTURE_WITH_ID_1); + } +{%endif%}{%endfor%} + @Test + void register_calls_save_on_repository_if_all_requirements_are_met_and_publishes_an_event() { + when({{ nameCamelCase }}Repository.save({{ nameScreamingSnakeCase }}_ENTITY_FIXTURE_1)) + .thenReturn({{ nameScreamingSnakeCase }}_ENTITY_FIXTURE_WITH_ID_1); + + when(passwordEncoder.encode(USER_ENTITY_FIXTURE_1.getPassword())) + .thenReturn(USER_ENTITY_FIXTURE_WITH_ID_1.getPassword()); +{%for field in fields%}{%if field.isFieldRelational%} + when({{ field.fieldTypeCamelCase }}Service.findById({{ nameScreamingSnakeCase }}_FIXTURE_WITH_ID_1.get{{ field.fieldNamePascalCase }}())) + .thenReturn(Optional.of({{ field.fieldTypeScreamingSnakeCase }}_FIXTURE_WITH_ID_1)); +{%endif%}{%endfor%} + var actual = {{ nameCamelCase }}Service.register(REGISTER_{{ nameScreamingSnakeCase }}_FIXTURE_1); + + assertThat(actual).isEqualTo({{ nameScreamingSnakeCase }}_FIXTURE_WITH_ID_1); + + verify(publisher) + .publishEvent(new {{ namePascalCase }}Event.{{ namePluralPascalCase }}RegisteredEvent({{ nameCamelCase }}Service, singletonList({{ nameScreamingSnakeCase }}_FIXTURE_WITH_ID_1))); + } +{%if hasRelations%} + @Test + void register_fails_when_related_objects_cannot_be_found_and_publishes_no_events() { + assertThrows(RuntimeException.class, () -> + {{ nameCamelCase }}Service.register(REGISTER_{{ nameScreamingSnakeCase }}_FIXTURE_1)); + + verify({{ nameCamelCase }}Repository, times(0)).save({{ nameScreamingSnakeCase }}_ENTITY_FIXTURE_1); + verify(publisher, times(0)).publishEvent(any()); + } +{%endif%} + @Test + void update_calls_save_on_repository_if_all_requirements_are_met_and_publishes_an_event() { + when({{ nameCamelCase }}Repository.findById(1)) + .thenReturn(Optional.of({{ nameScreamingSnakeCase }}_ENTITY_FIXTURE_WITH_ID_1)); + + when({{ nameCamelCase }}Repository.save({{ nameScreamingSnakeCase }}_ENTITY_FIXTURE_WITH_ID_1)) + .thenReturn({{ nameScreamingSnakeCase }}_ENTITY_FIXTURE_WITH_ID_1); +{%for field in fields%}{%if field.isFieldRelational%} + when({{ field.fieldTypeCamelCase }}Service.findById({{ nameScreamingSnakeCase }}_ENTITY_FIXTURE_WITH_ID_1.get{{ field.fieldNamePascalCase }}())) + .thenReturn(Optional.of({{ field.fieldTypeScreamingSnakeCase }}_FIXTURE_WITH_ID_1)); +{%endif%}{%endfor%} + var actual = {{ nameCamelCase }}Service.update(1, UPDATE_{{ nameScreamingSnakeCase }}_FIXTURE_1); + + assertThat(actual).isEqualTo(Optional.of({{ nameScreamingSnakeCase }}_FIXTURE_WITH_ID_1)); + + verify(publisher) + .publishEvent(new {{ namePascalCase }}Event.{{ namePluralPascalCase }}UpdatedEvent({{ nameCamelCase }}Service, singletonList({{ nameScreamingSnakeCase }}_FIXTURE_WITH_ID_1))); + } +{%if hasRelations%} + @Test + void update_fails_when_related_objects_cannot_be_found_and_publishes_no_events() { + assertThrows(RuntimeException.class, () -> + {{ nameCamelCase }}Service.update(1, UPDATE_{{ nameScreamingSnakeCase }}_FIXTURE_1)); + + verify({{ nameCamelCase }}Repository, times(0)).save({{ nameScreamingSnakeCase }}_ENTITY_FIXTURE_WITH_ID_1); + verify(publisher, times(0)).publishEvent(any()); + } +{%endif%} + @Test + void deleteById_deletes_the_{{ namePascalCase }}_identified_by_id_in_the_repository_if_it_exists_and_publishes_an_event() { + when({{ nameCamelCase }}Repository.findById(1)) + .thenReturn(Optional.of({{ nameScreamingSnakeCase }}_ENTITY_FIXTURE_WITH_ID_1)); + + {{ nameCamelCase }}Service.deleteById(1); + + verify({{ nameCamelCase }}Repository).deleteById(1); + + verify(publisher) + .publishEvent(new {{ namePascalCase }}Event.{{ namePluralPascalCase }}DeletedEvent({{ nameCamelCase }}Service, singletonList({{ nameScreamingSnakeCase }}_FIXTURE_WITH_ID_1))); + } + + @Test + void deleteById_does_not_delete_the_{{ namePascalCase }}_identified_by_id_in_the_repository_if_it_doesnt_exist_and_publishes_no_events() { + when({{ nameCamelCase }}Repository.findById(1)) + .thenReturn(Optional.empty()); + + {{ nameCamelCase }}Service.deleteById(1); + + verify({{ nameCamelCase }}Repository, times(0)).deleteById(1); + verify(publisher, times(0)).publishEvent(any()); + } +{%for field in fields%}{%if field.isFieldRelational%} + @Test + void deleteBy{{ field.fieldNamePascalCase }}_deletes_{{ field.fieldTypePluralCamelCase }}_by_{{ field.fieldNameCamelCase }}_and_publishes_event() { + when({{ nameCamelCase }}Repository.findBy{{ field.fieldNamePluralPascalCase }}(singletonList(10))) + .thenReturn(singletonList({{ nameScreamingSnakeCase }}_ENTITY_FIXTURE_WITH_ID_1)); + + var actual = {{ nameCamelCase }}Service.deleteBy{{ field.fieldNamePluralPascalCase }}(singletonList(10)); + assertThat(actual).isEqualTo(true); + + verify({{ nameCamelCase }}Repository).deleteAll(singletonList({{ nameScreamingSnakeCase }}_ENTITY_FIXTURE_WITH_ID_1)); + verify(publisher).publishEvent(new {{ namePascalCase }}Event.{{ namePluralPascalCase }}DeletedEvent({{ nameCamelCase }}Service, singletonList({{ nameScreamingSnakeCase }}_FIXTURE_WITH_ID_1))); + } + + @Test + void deleteBy{{ field.fieldNamePascalCase }}_returns_false_if_no_{{ field.fieldTypePluralCamelCase }}_are_deleted_and_publishes_no_events() { + when({{ nameCamelCase }}Repository.findBy{{ field.fieldNamePluralPascalCase }}(singletonList(10))) + .thenReturn(emptyList()); + + var actual = {{ nameCamelCase }}Service.deleteBy{{ field.fieldNamePluralPascalCase }}(singletonList(10)); + assertThat(actual).isEqualTo(false); + + verify({{ nameCamelCase }}Repository, times(0)).deleteAll(any()); + verify(publisher, times(0)).publishEvent(any()); + } +{%endif%}{%endfor%} +} diff --git a/pkg/generator/templates/backends/java/entity/[artifactId]-server/src/main/java/[groupIdSlashes]/[artifactIdSlashes]/domain/[nameLowerCase]/[namePascalCase].java.peb b/pkg/generator/templates/backends/java/entity/[artifactId]-server/src/main/java/[groupIdSlashes]/[artifactIdSlashes]/domain/[nameLowerCase]/[namePascalCase].java.peb index 98d6c56..3e22c91 100644 --- a/pkg/generator/templates/backends/java/entity/[artifactId]-server/src/main/java/[groupIdSlashes]/[artifactIdSlashes]/domain/[nameLowerCase]/[namePascalCase].java.peb +++ b/pkg/generator/templates/backends/java/entity/[artifactId]-server/src/main/java/[groupIdSlashes]/[artifactIdSlashes]/domain/[nameLowerCase]/[namePascalCase].java.peb @@ -11,10 +11,10 @@ import java.time.OffsetDateTime; @With @Builder public class {{ namePascalCase }} { - private int id;{%for field in fields%} + private final int id;{%for field in fields%} @NonNull - private {{ field.fieldJavaType }} {{ field.fieldNameCamelCase }};{%endfor%} + private final {{ field.fieldJavaType }} {{ field.fieldNameCamelCase }};{%endfor%} @NonNull - private OffsetDateTime createdAt; - private OffsetDateTime updatedAt; + private final OffsetDateTime createdAt; + private final OffsetDateTime updatedAt; } diff --git a/pkg/generator/templates/backends/java/entity/[artifactId]-server/src/main/java/[groupIdSlashes]/[artifactIdSlashes]/domain/[nameLowerCase]/[namePascalCase]Entity.java.peb b/pkg/generator/templates/backends/java/entity/[artifactId]-server/src/main/java/[groupIdSlashes]/[artifactIdSlashes]/domain/[nameLowerCase]/[namePascalCase]Entity.java.peb index 80691b9..f2eb572 100644 --- a/pkg/generator/templates/backends/java/entity/[artifactId]-server/src/main/java/[groupIdSlashes]/[artifactIdSlashes]/domain/[nameLowerCase]/[namePascalCase]Entity.java.peb +++ b/pkg/generator/templates/backends/java/entity/[artifactId]-server/src/main/java/[groupIdSlashes]/[artifactIdSlashes]/domain/[nameLowerCase]/[namePascalCase]Entity.java.peb @@ -17,11 +17,11 @@ import java.time.LocalDateTime; @Table("{{ namePluralSnakeCase }}") public class {{ namePascalCase }}Entity { @Id - private Integer id;{%for field in fields%} + private final Integer id;{%for field in fields%} @NonNull - private {{ field.fieldJavaType }} {{ field.fieldNameCamelCase }};{%endfor%} + private final {{ field.fieldJavaType }} {{ field.fieldNameCamelCase }};{%endfor%} @CreatedDate - private LocalDateTime createdAt; + private final LocalDateTime createdAt; @LastModifiedDate - private LocalDateTime updatedAt; + private final LocalDateTime updatedAt; } diff --git a/pkg/generator/templates/backends/java/graphql/[artifactId]-server/src/main/java/[groupIdSlashes]/[artifactIdSlashes]/application/graphql/[nameLowerCase]/[namePascalCase]Resolver.java.peb b/pkg/generator/templates/backends/java/graphql/[artifactId]-server/src/main/java/[groupIdSlashes]/[artifactIdSlashes]/application/graphql/[nameLowerCase]/[namePascalCase]Resolver.java.peb index af2b2fd..87cbed2 100644 --- a/pkg/generator/templates/backends/java/graphql/[artifactId]-server/src/main/java/[groupIdSlashes]/[artifactIdSlashes]/application/graphql/[nameLowerCase]/[namePascalCase]Resolver.java.peb +++ b/pkg/generator/templates/backends/java/graphql/[artifactId]-server/src/main/java/[groupIdSlashes]/[artifactIdSlashes]/application/graphql/[nameLowerCase]/[namePascalCase]Resolver.java.peb @@ -31,7 +31,10 @@ public class {{ namePascalCase }}Resolver { @DgsQuery Iterable<{{ namePascalCase }}> {{ namePluralCamelCase }}({%if hasRelations%}{{ namePascalCase }}FilterInput filter{%endif%}) { - return {{ nameCamelCase }}Service.findAll({%if hasRelations%}filter{%endif%}); + {%if hasRelations%}if (filter == null) { + return {{ nameCamelCase }}Service.findAll(); + } + {%endif%}return {{ nameCamelCase }}Service.findAll({%if hasRelations%}filter{%endif%}); } @DgsMutation diff --git a/pkg/generator/templates/backends/java/graphql/[artifactId]-server/src/test/java/[groupIdSlashes]/[artifactIdSlashes]/application/graphql/[nameLowerCase]/[namePascalCase]ResolverTests.java.peb b/pkg/generator/templates/backends/java/graphql/[artifactId]-server/src/test/java/[groupIdSlashes]/[artifactIdSlashes]/application/graphql/[nameLowerCase]/[namePascalCase]ResolverTests.java.peb index 0abf76c..19dbe80 100644 --- a/pkg/generator/templates/backends/java/graphql/[artifactId]-server/src/test/java/[groupIdSlashes]/[artifactIdSlashes]/application/graphql/[nameLowerCase]/[namePascalCase]ResolverTests.java.peb +++ b/pkg/generator/templates/backends/java/graphql/[artifactId]-server/src/test/java/[groupIdSlashes]/[artifactIdSlashes]/application/graphql/[nameLowerCase]/[namePascalCase]ResolverTests.java.peb @@ -104,7 +104,7 @@ class {{ namePascalCase }}ResolverTests { @Test void delete{{ namePascalCase }}() { - when({{ nameCamelCase }}Service.deleteById(any())) + when({{ nameCamelCase }}Service.deleteById(anyInt())) .thenReturn(true); var result = dgsQueryExecutor.executeAndExtractJsonPath( diff --git a/pkg/generator/templates/backends/java/new/[artifactId]-server/src/main/java/[groupIdSlashes]/[artifactIdSlashes]/Application.java.peb b/pkg/generator/templates/backends/java/new/[artifactId]-server/src/main/java/[groupIdSlashes]/[artifactIdSlashes]/Application.java.peb index 81d992a..72fcbc0 100644 --- a/pkg/generator/templates/backends/java/new/[artifactId]-server/src/main/java/[groupIdSlashes]/[artifactIdSlashes]/Application.java.peb +++ b/pkg/generator/templates/backends/java/new/[artifactId]-server/src/main/java/[groupIdSlashes]/[artifactIdSlashes]/Application.java.peb @@ -2,7 +2,9 @@ package {{ groupId }}.{{ artifactId }}; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.data.jdbc.repository.config.EnableJdbcAuditing; +@EnableJdbcAuditing @SpringBootApplication public class Application { diff --git a/pkg/generator/templates/backends/java/service/[artifactId]-server/src/main/java/[groupIdSlashes]/[artifactIdSlashes]/domain/[nameLowerCase]/[namePascalCase]Event.java.peb b/pkg/generator/templates/backends/java/service/[artifactId]-server/src/main/java/[groupIdSlashes]/[artifactIdSlashes]/domain/[nameLowerCase]/[namePascalCase]Event.java.peb index 986262a..b648f9d 100644 --- a/pkg/generator/templates/backends/java/service/[artifactId]-server/src/main/java/[groupIdSlashes]/[artifactIdSlashes]/domain/[nameLowerCase]/[namePascalCase]Event.java.peb +++ b/pkg/generator/templates/backends/java/service/[artifactId]-server/src/main/java/[groupIdSlashes]/[artifactIdSlashes]/domain/[nameLowerCase]/[namePascalCase]Event.java.peb @@ -1,10 +1,12 @@ package {{ groupId }}.{{ artifactId }}.domain.{{ nameLowerCase }}; +import lombok.EqualsAndHashCode; import lombok.Getter; import org.springframework.context.ApplicationEvent; import java.util.List; +@EqualsAndHashCode(callSuper = false) public abstract class {{ namePascalCase }}Event extends ApplicationEvent { {{ namePascalCase }}Event(Object source) { super(source); diff --git a/pkg/generator/templates/backends/java/service/[artifactId]-server/src/main/java/[groupIdSlashes]/[artifactIdSlashes]/domain/[nameLowerCase]/[namePascalCase]Service.java.peb b/pkg/generator/templates/backends/java/service/[artifactId]-server/src/main/java/[groupIdSlashes]/[artifactIdSlashes]/domain/[nameLowerCase]/[namePascalCase]Service.java.peb index e4b2b73..9bffe67 100644 --- a/pkg/generator/templates/backends/java/service/[artifactId]-server/src/main/java/[groupIdSlashes]/[artifactIdSlashes]/domain/[nameLowerCase]/[namePascalCase]Service.java.peb +++ b/pkg/generator/templates/backends/java/service/[artifactId]-server/src/main/java/[groupIdSlashes]/[artifactIdSlashes]/domain/[nameLowerCase]/[namePascalCase]Service.java.peb @@ -48,7 +48,7 @@ public class {{ namePascalCase }}Service { } {%endif%}{%endfor%}{%if hasRelations%} public List<{{ namePascalCase }}> findAll() { - return findAll(null); + return findAll({{ namePascalCase }}FilterInput.builder().build()); } {%endif%} public List<{{ namePascalCase }}> findAll({%if hasRelations%}{{ namePascalCase }}FilterInput filterInput{%endif%}) { {%for field in fields%}{%if field.isFieldRelational%} @@ -129,6 +129,8 @@ public class {{ namePascalCase }}Service { @Data @With @Builder + @NoArgsConstructor + @AllArgsConstructor public static class Create{{ namePascalCase }}Input {{ "{" }}{%for field in fields%} @NonNull private {{ field.fieldJavaType }} {{ field.fieldNameCamelCase }};{%endfor%} @@ -137,6 +139,8 @@ public class {{ namePascalCase }}Service { @Data @With @Builder + @NoArgsConstructor + @AllArgsConstructor public static class Update{{ namePascalCase }}Input {{ "{" }}{%for field in fields%} private {{ field.fieldJavaType }} {{ field.fieldNameCamelCase }};{%endfor%} {{ "}" }}{%if hasRelations%} @@ -144,6 +148,8 @@ public class {{ namePascalCase }}Service { @Data @With @Builder + @NoArgsConstructor + @AllArgsConstructor public static class {{ namePascalCase }}FilterInput {{ "{" }}{%for field in fields%}{%if field.isFieldRelational%} private {{ field.fieldJavaType }} {{ field.fieldNameCamelCase }};{%endif%}{%endfor%} {{ "}" }}{%endif%} diff --git a/pkg/generator/templates/backends/java/service/[artifactId]-server/src/test/java/[groupIdSlashes]/[artifactIdSlashes]/domain/[nameLowerCase]/[namePascalCase]ServiceUnitTests.java.peb b/pkg/generator/templates/backends/java/service/[artifactId]-server/src/test/java/[groupIdSlashes]/[artifactIdSlashes]/domain/[nameLowerCase]/[namePascalCase]ServiceUnitTests.java.peb index ff3b5b9..2306de6 100644 --- a/pkg/generator/templates/backends/java/service/[artifactId]-server/src/test/java/[groupIdSlashes]/[artifactIdSlashes]/domain/[nameLowerCase]/[namePascalCase]ServiceUnitTests.java.peb +++ b/pkg/generator/templates/backends/java/service/[artifactId]-server/src/test/java/[groupIdSlashes]/[artifactIdSlashes]/domain/[nameLowerCase]/[namePascalCase]ServiceUnitTests.java.peb @@ -47,7 +47,7 @@ class {{ namePascalCase }}ServiceUnitTests { var actual = {{ nameCamelCase }}Service.findById(1); - assertThat(actual).isEqualTo({{ nameScreamingSnakeCase }}_FIXTURE_WITH_ID_1); + assertThat(actual).isEqualTo(Optional.of({{ nameScreamingSnakeCase }}_FIXTURE_WITH_ID_1)); } @Test @@ -57,7 +57,7 @@ class {{ namePascalCase }}ServiceUnitTests { var actual = {{ nameCamelCase }}Service.findById(1); - assertThat(actual).isNull(); + assertThat(actual).isEmpty(); verify(publisher, times(0)) .publishEvent(any()); @@ -151,7 +151,7 @@ class {{ namePascalCase }}ServiceUnitTests { {%endif%}{%endfor%} var actual = {{ nameCamelCase }}Service.update(1, UPDATE_{{ nameScreamingSnakeCase }}_FIXTURE_1); - assertThat(actual).isEqualTo({{ nameScreamingSnakeCase }}_FIXTURE_WITH_ID_1); + assertThat(actual).isEqualTo(Optional.of({{ nameScreamingSnakeCase }}_FIXTURE_WITH_ID_1)); verify(publisher) .publishEvent(new {{ namePascalCase }}Event.{{ namePluralPascalCase }}UpdatedEvent({{ nameCamelCase }}Service, singletonList({{ nameScreamingSnakeCase }}_FIXTURE_WITH_ID_1)));