Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 24 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,37 +17,46 @@

## 必要なソフトウエア

### Windows
本プロジェクトの開発をするには以下のソフトウエアが必要です。
公式サイトからダウンロードしてインストールしてください。

以下のソフトウエアを、公式サイトからダウンロードしてインストールしてください。

- Java Development Kit 21 (以下うちどれか一つを選択してインストール)
- Java Development Kit (JDK) 17 以降
- [Oracle Java SE](https://www.oracle.com/jp/java/technologies/java-se-glance.html)
- [Amazon Corrette](https://aws.amazon.com/jp/corretto)
- [Eclipse Temurin](https://adoptium.net/temurin/releases/)
- [Visual Studio Code](https://azure.microsoft.com/ja-jp/products/visual-studio-code)
- IDEもしくはテキストエディタ
- [Eclopse](https://www.eclipse.org/downloads/)
- [Pleiades](https://willbrains.jp/) - Pleiades All in One - Eclipse日本語化プラグイン+α
- [Visual Studio Code](https://azure.microsoft.com/ja-jp/products/visual-studio-code)
- [Git for Windows](https://gitforwindows.org/)
- [Docker Desktop][https://www.docker.com/ja-jp/products/docker-desktop/]

### Linux(Ubuntu)
## あると良いソフトウエア

以下のコマンドを実行して、必要なソフトウエアをインストールしてください。
- コンテナ化プラットフォーム ※コンテナ(DevContainer)環境で開発する場合に必要になります。
- [Docker Desktop][https://www.docker.com/ja-jp/products/docker-desktop/]
- [Docker Engine](https://docs.docker.com/engine/install/)

```sh
sudo apt install git openjdk-21-jdk-headless
wget -o vscode.deb https://code.visualstudio.com/sha/download?build=stable&os=linux-deb-x64
sudo apt install ./vscode.deb
```

## 開発方法

本プロジェクトは、 Visual Studio Code(以下、VSCode) で開発することを想定しています。
本プロジェクトは、 EclipseもしくはVisual Studio Code(以下、VSCode) で開発することを想定しています。
なお、開発ドキュメントは所定の場所に格納しています。事前に講師が説明をしますので、その場所のドキュメントを参照してください。

### Eclipse

本プロジェクトを適当な場所にクローンした後、Eclipseを起動して、インポート機能の「既存Mavenプロジェクト」で本プロジェクトをワークスペースにインポートします。

### VSCode

VSCode を起動し、フォークしたリポジトリを適当な場所にクローンしてください。その後、クローンしたフォルダを開きます。
初回クローンしたフォルダを VSCode で開くと、本プロジェクトが利用している拡張機能のインストールを求められますので、インストールしてください。

なお、開発ドキュメントは所定の場所に格納しています。事前に講師が説明をしますので、その場所のドキュメントを参照してください。

## 実行およびデバッグ方法

### Eclipse

Eclipseでインポートしたプロジェクトで、Alt+Shift+Xを同時押しした後、bキーを押します。

### VSCode

Expand Down
7 changes: 4 additions & 3 deletions batch/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,13 @@
<artifactId>training</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<groupId>com.s_giken.training</groupId>
<groupId>com.s_giken.training.batch</groupId>
<artifactId>batch</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>batch</name>
<description>研修用バッチプログラム</description>
<properties>
<java.version>21</java.version>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
Expand Down Expand Up @@ -52,6 +51,8 @@
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
<annotationProcessorPaths>
<path>
<groupId>org.projectlombok</groupId>
Expand Down
Empty file modified mvnw
100644 → 100755
Empty file.
4 changes: 2 additions & 2 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.4.4</version>
<relativePath/> <!-- lookup parent from repository -->
<version>3.5.3</version>
<relativePath/>
</parent>
<groupId>com.s_giken.training</groupId>
<artifactId>training</artifactId>
Expand Down
1 change: 1 addition & 0 deletions webapp/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/.apt_generated_tests/
16 changes: 12 additions & 4 deletions webapp/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,13 @@
<artifactId>training</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<groupId>com.s_giken.training</groupId>
<groupId>com.s_giken.training.webapp</groupId>
<artifactId>webapp</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>war</packaging>
<name>webapp</name>
<description>実習用Webプログラム</description>
<description>実習用Webアプリ</description>
<properties>
<java.version>21</java.version>
<java.version>17</java.version>
</properties>
<dependencies>

Expand Down Expand Up @@ -65,11 +64,18 @@
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>

<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>

</dependencies>

<build>
Expand All @@ -78,6 +84,8 @@
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
<annotationProcessorPaths>
<path>
<groupId>org.projectlombok</groupId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;

/**
Expand All @@ -28,7 +29,7 @@ public class SecurityConfig {
* @throws Exception 例外全般
*/
@Bean
public SecurityFilterChain webSecurityFilterChain(HttpSecurity http) throws Exception {
SecurityFilterChain webSecurityFilterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf.disable()) // CSRF対策を無効化
.headers((header) -> header.frameOptions((frame) -> frame.disable()))
Expand All @@ -41,7 +42,8 @@ public SecurityFilterChain webSecurityFilterChain(HttpSecurity http) throws Exce
.logout((logout) -> logout
.logoutSuccessUrl("/"))
.authorizeHttpRequests((authorize) -> authorize
.requestMatchers(AntPathRequestMatcher.antMatcher("/h2-console/**"))
.requestMatchers(
PathPatternRequestMatcher.withDefaults().matcher("/h2-console/**"))
.permitAll()
.anyRequest().authenticated());

Expand All @@ -58,7 +60,7 @@ public SecurityFilterChain webSecurityFilterChain(HttpSecurity http) throws Exce
* @return ログインユーザー情報
*/
@Bean
public UserDetailsService users() {
UserDetailsService users() {
var user = User
.builder()
.username("user")
Expand All @@ -74,7 +76,7 @@ public UserDetailsService users() {
* @return パスワードをハッシュ化するエンコーダーのオブジェクト
*/
@Bean
public PasswordEncoder passwordEncoder() {
PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
4 changes: 4 additions & 0 deletions webapp/src/main/resources/application-dev.properties
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ spring.jpa.hibernate.ddl-auto=update
# SQLのログを出力する
spring.jpa.show-sql=true

# H2コンソールを有効にする
# (http://localhost:8080/h2-console でアクセス可能)
spring.h2.console.enabled=true

# ThymeleafのテンプレートキャッシュをOFFにする
# (デバッグ時、テンプレート修正した際に即時反映させるため)
spring.thymeleaf.cache=false
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
package com.s_giken.training.webapp.controller;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.when;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf;
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.result.MockMvcResultMatchers.model;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.view;

import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Optional;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.data.jpa.mapping.JpaMetamodelMappingContext;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.bean.override.mockito.MockitoBean;
import org.springframework.test.web.servlet.MockMvc;

import com.s_giken.training.webapp.model.entity.Member;
import com.s_giken.training.webapp.model.entity.MemberSearchCondition;
import com.s_giken.training.webapp.service.MemberService;

@WebMvcTest(MemberController.class)
@WithMockUser
@ActiveProfiles("test")
class MemberControllerTest {

@Autowired
private MockMvc mockMvc;

@MockitoBean
private MemberService memberService;

@MockitoBean
private JpaMetamodelMappingContext jpaMetamodelMappingContext;

private Member testMember;
private List<Member> testMembers;

@BeforeEach
void setUp() {
testMember = new Member();
testMember.setMemberId(1);
testMember.setMail("test@example.com");
testMember.setName("テストユーザー");
testMember.setAddress("東京都");
testMember.setStartDate(new Date());
testMember.setPaymentMethod(1);

Member testMember2 = new Member();
testMember2.setMemberId(2);
testMember2.setMail("test2@example.com");
testMember2.setName("テストユーザー2");
testMember2.setAddress("大阪府");
testMember2.setStartDate(new Date());
testMember2.setPaymentMethod(2);

testMembers = Arrays.asList(testMember, testMember2);
}

@Test
void testShowSearchCondition() throws Exception {
mockMvc.perform(get("/member/search"))
.andExpect(status().isOk())
.andExpect(view().name("member_search_condition"))
.andExpect(model().attributeExists("memberSearchCondition"));
}

@Test
void testSearchAndListing() throws Exception {
when(memberService.findByConditions(any(MemberSearchCondition.class)))
.thenReturn(testMembers);

mockMvc.perform(post("/member/search")
.param("mail", "test")
.with(csrf()))
.andExpect(status().isOk())
.andExpect(view().name("member_search_result"))
.andExpect(model().attributeExists("result"))
.andExpect(model().attribute("result", testMembers));
}

@Test
void testEditMember_存在する場合() throws Exception {
when(memberService.findById(1)).thenReturn(Optional.of(testMember));

mockMvc.perform(get("/member/edit/1"))
.andExpect(status().isOk())
.andExpect(view().name("member_edit"))
.andExpect(model().attributeExists("member"));
}

@Test
void testEditMember_存在しない場合() throws Exception {
when(memberService.findById(999)).thenReturn(Optional.empty());

mockMvc.perform(get("/member/edit/999"))
.andExpect(status().isNotFound());
}

@Test
void testAddMember() throws Exception {
mockMvc.perform(get("/member/add"))
.andExpect(status().isOk())
.andExpect(view().name("member_edit"))
.andExpect(model().attributeExists("member"));
}

@Test
void testSaveMember_正常な場合() throws Exception {
doNothing().when(memberService).save(any(Member.class));

mockMvc.perform(post("/member/save")
.param("memberId", "1")
.param("mail", "test@example.com")
.param("name", "テストユーザー")
.param("address", "東京都")
.param("startDate", "2024-01-01")
.param("paymentMethod", "1")
.with(csrf()))
.andExpect(status().is3xxRedirection())
.andExpect(redirectedUrl("/member/edit/1"));
}

@Test
void testSaveMember_バリデーションエラー() throws Exception {
mockMvc.perform(post("/member/save")
.param("memberId", "1")
.param("mail", "")
.param("name", "")
.param("address", "")
.param("startDate", "")
.param("paymentMethod", "")
.with(csrf()))
.andExpect(status().isOk())
.andExpect(view().name("member_edit"));
}

@Test
void testDeleteMember_存在する場合() throws Exception {
when(memberService.findById(1)).thenReturn(Optional.of(testMember));
doNothing().when(memberService).deleteById(1);

mockMvc.perform(get("/member/delete/1"))
.andExpect(status().is3xxRedirection())
.andExpect(redirectedUrl("/member/search"));
}

@Test
void testDeleteMember_存在しない場合() throws Exception {
when(memberService.findById(999)).thenReturn(Optional.empty());

mockMvc.perform(get("/member/delete/999"))
.andExpect(status().isNotFound());
}
}
Loading