├── .gitignore ├── Makefile ├── README.md ├── build ├── db │ ├── Dockerfile │ ├── custom.cnf │ └── sql │ │ ├── 00_grant_to_app_user.sql │ │ ├── 01_todo.sql │ │ └── 99_insert_data.sql ├── docker-compose.yml └── sample-api │ └── Dockerfile ├── cmd └── sample-api │ └── main.go ├── controller ├── dto │ └── todo_dto.go ├── router.go ├── router_test.go ├── todo_controller.go └── todo_controller_test.go ├── go.mod ├── go.sum ├── model ├── entity │ └── todo_entity.go └── repository │ ├── database.go │ └── todo_repository.go ├── test └── mock.go └── test_results └── .gitkeep /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | 3 | # Binaries for programs and plugins 4 | *.exe 5 | *.exe~ 6 | *.dll 7 | *.so 8 | *.dylib 9 | 10 | # Test binary, built with `go test -c` 11 | *.test 12 | 13 | # Output of the go coverage tool, specifically when used with LiteIDE 14 | *.out 15 | *.html 16 | 17 | # Dependency directories (remove the comment below to include it) 18 | # vendor/ 19 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | format: 2 | @find . -print | grep --regex '.*\.go' | xargs goimports -w -local "github.com/koga456/sample-api" 3 | verify: 4 | @staticcheck ./... && go vet ./... 5 | unit-test: 6 | @go test ./... -coverprofile=./test_results/cover.out && go tool cover -html=./test_results/cover.out -o ./test_results/cover.html 7 | serve: 8 | @docker-compose -f build/docker-compose.yml up -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # sample-api 2 | - 基本的なCRUD操作を行うREST APIです。  3 | - TODOアプリのバックエンドのイメージでTODO(タイトルと内容)の取得/追加/更新/削除が行えます。 4 | -------------------------------------------------------------------------------- /build/db/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM --platform=linux/amd64 library/mysql:8.0.25 2 | 3 | ENV MYSQL_DATABASE todo 4 | 5 | COPY custom.cnf /etc/mysql/conf.d/ 6 | 7 | COPY sql /docker-entrypoint-initdb.d 8 | -------------------------------------------------------------------------------- /build/db/custom.cnf: -------------------------------------------------------------------------------- 1 | [mysqld] 2 | lower_case_table_names=1 3 | explicit_defaults_for_timestamp=true 4 | 5 | skip_ssl 6 | character-set-server=utf8mb4 7 | collation-server=utf8mb4_bin 8 | init_connect='SET collation_connection = utf8mb4_bin; SET NAMES utf8mb4' 9 | 10 | [client] 11 | default_character_set=utf8mb4 12 | -------------------------------------------------------------------------------- /build/db/sql/00_grant_to_app_user.sql: -------------------------------------------------------------------------------- 1 | CREATE USER 'todo-app'@'%' IDENTIFIED BY 'todo-password'; 2 | GRANT SELECT,INSERT,UPDATE,DELETE ON todo.* TO 'todo-app'@'%'; -------------------------------------------------------------------------------- /build/db/sql/01_todo.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE IF NOT EXISTS todo ( 2 | id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, 3 | title VARCHAR(40) NOT NULL, 4 | content VARCHAR(100) NOT NULL, 5 | created_at TIMESTAMP NOT NULL DEFAULT current_timestamp, 6 | updated_at TIMESTAMP NOT NULL DEFAULT current_timestamp ON UPDATE current_timestamp 7 | ) -------------------------------------------------------------------------------- /build/db/sql/99_insert_data.sql: -------------------------------------------------------------------------------- 1 | INSERT INTO todo (title, content) VALUES ('買い物', '今日の帰りに夕食の材料を買う'); 2 | INSERT INTO todo (title, content) VALUES ('勉強', 'TOEICの勉強を1時間やる'); 3 | INSERT INTO todo (title, content) VALUES ('ゴミ出し', '次の火曜日は燃えないゴミの日なので忘れないように'); -------------------------------------------------------------------------------- /build/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '2' 2 | services: 3 | db: 4 | image: sample-api-db 5 | container_name: sample-api-db 6 | build: db 7 | environment: 8 | MYSQL_ROOT_PASSWORD: todo-password 9 | TZ: "UTC" 10 | ports: 11 | - "127.0.0.1:3306:3306" 12 | api-server: 13 | image: sample-api-server 14 | container_name: sample-api-server 15 | build: 16 | context: ../ 17 | dockerfile: ./build/sample-api/Dockerfile 18 | ports: 19 | - "127.0.0.1:8080:8080" 20 | depends_on: 21 | - db -------------------------------------------------------------------------------- /build/sample-api/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.17.1-alpine as builder 2 | 3 | WORKDIR /build 4 | COPY ../../go.mod ../../go.sum ./ 5 | RUN go mod download 6 | COPY ../../ ./ 7 | 8 | ARG CGO_ENABLED=0 9 | ARG GOOS=linux 10 | ARG GOARCH=amd64 11 | RUN go build -ldflags '-s -w' ./cmd/sample-api 12 | 13 | FROM alpine 14 | COPY --from=builder /build/sample-api /opt/app/ 15 | ENTRYPOINT ["/opt/app/sample-api"] -------------------------------------------------------------------------------- /cmd/sample-api/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/koga456/sample-api/controller" 7 | "github.com/koga456/sample-api/model/repository" 8 | ) 9 | 10 | var tr = repository.NewTodoRepository() 11 | var tc = controller.NewTodoController(tr) 12 | var ro = controller.NewRouter(tc) 13 | 14 | func main() { 15 | server := http.Server{ 16 | Addr: ":8080", 17 | } 18 | http.HandleFunc("/todos/", ro.HandleTodosRequest) 19 | server.ListenAndServe() 20 | } 21 | -------------------------------------------------------------------------------- /controller/dto/todo_dto.go: -------------------------------------------------------------------------------- 1 | package dto 2 | 3 | type TodoResponse struct { 4 | Id int `json:"id"` 5 | Title string `json:"title"` 6 | Content string `json:"content"` 7 | } 8 | 9 | type TodoRequest struct { 10 | Title string `json:"title"` 11 | Content string `json:"content"` 12 | } 13 | 14 | type TodosResponse struct { 15 | Todos []TodoResponse `json:"todos"` 16 | } 17 | -------------------------------------------------------------------------------- /controller/router.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | import ( 4 | "net/http" 5 | ) 6 | 7 | type Router interface { 8 | HandleTodosRequest(w http.ResponseWriter, r *http.Request) 9 | } 10 | 11 | type router struct { 12 | tc TodoController 13 | } 14 | 15 | func NewRouter(tc TodoController) Router { 16 | return &router{tc} 17 | } 18 | 19 | func (ro *router) HandleTodosRequest(w http.ResponseWriter, r *http.Request) { 20 | switch r.Method { 21 | case "GET": 22 | ro.tc.GetTodos(w, r) 23 | case "POST": 24 | ro.tc.PostTodo(w, r) 25 | case "PUT": 26 | ro.tc.PutTodo(w, r) 27 | case "DELETE": 28 | ro.tc.DeleteTodo(w, r) 29 | default: 30 | w.WriteHeader(405) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /controller/router_test.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | import ( 4 | "net/http" 5 | "net/http/httptest" 6 | "os" 7 | "strings" 8 | "testing" 9 | 10 | "github.com/koga456/sample-api/test" 11 | ) 12 | 13 | var mux *http.ServeMux 14 | 15 | func TestMain(m *testing.M) { 16 | setUp() 17 | code := m.Run() 18 | os.Exit(code) 19 | } 20 | 21 | func setUp() { 22 | target := NewRouter(&test.MockTodoController{}) 23 | mux = http.NewServeMux() 24 | mux.HandleFunc("/todos/", target.HandleTodosRequest) 25 | 26 | } 27 | 28 | func TestGetTodos(t *testing.T) { 29 | r, _ := http.NewRequest("GET", "/todos/", nil) 30 | w := httptest.NewRecorder() 31 | 32 | mux.ServeHTTP(w, r) 33 | 34 | if w.Code != 200 { 35 | t.Errorf("Response cod is %v", w.Code) 36 | } 37 | } 38 | 39 | func TestPostTodo(t *testing.T) { 40 | json := strings.NewReader(`{"title":"test-title","content":"test-content"}`) 41 | r, _ := http.NewRequest("POST", "/todos/", json) 42 | w := httptest.NewRecorder() 43 | 44 | mux.ServeHTTP(w, r) 45 | 46 | if w.Code != 201 { 47 | t.Errorf("Response cod is %v", w.Code) 48 | } 49 | } 50 | 51 | func TestPutTodo(t *testing.T) { 52 | json := strings.NewReader(`{"title":"test-title","contents":"test-content"}`) 53 | r, _ := http.NewRequest("PUT", "/todos/2", json) 54 | w := httptest.NewRecorder() 55 | 56 | mux.ServeHTTP(w, r) 57 | 58 | if w.Code != 204 { 59 | t.Errorf("Response cod is %v", w.Code) 60 | } 61 | } 62 | 63 | func TestDeleteTodo(t *testing.T) { 64 | r, _ := http.NewRequest("DELETE", "/todos/2", nil) 65 | w := httptest.NewRecorder() 66 | 67 | mux.ServeHTTP(w, r) 68 | 69 | if w.Code != 204 { 70 | t.Errorf("Response cod is %v", w.Code) 71 | } 72 | } 73 | 74 | func TestInvalidMethod(t *testing.T) { 75 | r, _ := http.NewRequest("PATCH", "/todos/", nil) 76 | w := httptest.NewRecorder() 77 | 78 | mux.ServeHTTP(w, r) 79 | 80 | if w.Code != 405 { 81 | t.Errorf("Response cod is %v", w.Code) 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /controller/todo_controller.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | import ( 4 | "encoding/json" 5 | "net/http" 6 | "path" 7 | "strconv" 8 | 9 | "github.com/koga456/sample-api/controller/dto" 10 | "github.com/koga456/sample-api/model/entity" 11 | "github.com/koga456/sample-api/model/repository" 12 | ) 13 | 14 | type TodoController interface { 15 | GetTodos(w http.ResponseWriter, r *http.Request) 16 | PostTodo(w http.ResponseWriter, r *http.Request) 17 | PutTodo(w http.ResponseWriter, r *http.Request) 18 | DeleteTodo(w http.ResponseWriter, r *http.Request) 19 | } 20 | 21 | type todoController struct { 22 | tr repository.TodoRepository 23 | } 24 | 25 | func NewTodoController(tr repository.TodoRepository) TodoController { 26 | return &todoController{tr} 27 | } 28 | 29 | func (tc *todoController) GetTodos(w http.ResponseWriter, r *http.Request) { 30 | todos, err := tc.tr.GetTodos() 31 | if err != nil { 32 | w.WriteHeader(500) 33 | return 34 | } 35 | 36 | var todoResponses []dto.TodoResponse 37 | for _, v := range todos { 38 | todoResponses = append(todoResponses, dto.TodoResponse{Id: v.Id, Title: v.Title, Content: v.Content}) 39 | } 40 | 41 | var todosResponse dto.TodosResponse 42 | todosResponse.Todos = todoResponses 43 | 44 | output, _ := json.MarshalIndent(todosResponse.Todos, "", "\t\t") 45 | 46 | w.Header().Set("Content-Type", "application/json") 47 | w.Write(output) 48 | } 49 | 50 | func (tc *todoController) PostTodo(w http.ResponseWriter, r *http.Request) { 51 | body := make([]byte, r.ContentLength) 52 | r.Body.Read(body) 53 | var todoRequest dto.TodoRequest 54 | json.Unmarshal(body, &todoRequest) 55 | 56 | todo := entity.TodoEntity{Title: todoRequest.Title, Content: todoRequest.Content} 57 | id, err := tc.tr.InsertTodo(todo) 58 | if err != nil { 59 | w.WriteHeader(500) 60 | return 61 | } 62 | 63 | w.Header().Set("Location", r.Host+r.URL.Path+strconv.Itoa(id)) 64 | w.WriteHeader(201) 65 | } 66 | 67 | func (tc *todoController) PutTodo(w http.ResponseWriter, r *http.Request) { 68 | todoId, err := strconv.Atoi(path.Base(r.URL.Path)) 69 | if err != nil { 70 | w.WriteHeader(400) 71 | return 72 | } 73 | 74 | body := make([]byte, r.ContentLength) 75 | r.Body.Read(body) 76 | var todoRequest dto.TodoRequest 77 | json.Unmarshal(body, &todoRequest) 78 | 79 | todo := entity.TodoEntity{Id: todoId, Title: todoRequest.Title, Content: todoRequest.Content} 80 | err = tc.tr.UpdateTodo(todo) 81 | if err != nil { 82 | w.WriteHeader(500) 83 | return 84 | } 85 | 86 | w.WriteHeader(204) 87 | } 88 | 89 | func (tc *todoController) DeleteTodo(w http.ResponseWriter, r *http.Request) { 90 | todoId, err := strconv.Atoi(path.Base(r.URL.Path)) 91 | if err != nil { 92 | w.WriteHeader(400) 93 | return 94 | } 95 | 96 | err = tc.tr.DeleteTodo(todoId) 97 | if err != nil { 98 | w.WriteHeader(500) 99 | return 100 | } 101 | 102 | w.WriteHeader(204) 103 | } 104 | -------------------------------------------------------------------------------- /controller/todo_controller_test.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | import ( 4 | "encoding/json" 5 | "net/http/httptest" 6 | "strings" 7 | "testing" 8 | 9 | "github.com/koga456/sample-api/controller/dto" 10 | "github.com/koga456/sample-api/test" 11 | ) 12 | 13 | func TestGetTodos_NotFound(t *testing.T) { 14 | w := httptest.NewRecorder() 15 | r := httptest.NewRequest("GET", "/todos/", nil) 16 | 17 | target := NewTodoController(&test.MockTodoRepository{}) 18 | target.GetTodos(w, r) 19 | 20 | if w.Code != 200 { 21 | t.Errorf("Response cod is %v", w.Code) 22 | } 23 | if w.Header().Get("Content-Type") != "application/json" { 24 | t.Errorf("Content-Type is %v", w.Header().Get("Content-Type")) 25 | } 26 | 27 | body := make([]byte, w.Body.Len()) 28 | w.Body.Read(body) 29 | var todosResponse dto.TodosResponse 30 | json.Unmarshal(body, &todosResponse) 31 | if len(todosResponse.Todos) != 0 { 32 | t.Errorf("Response is %v", todosResponse.Todos) 33 | } 34 | } 35 | 36 | func TestGetTodos_ExistTodo(t *testing.T) { 37 | w := httptest.NewRecorder() 38 | r := httptest.NewRequest("GET", "/todos/", nil) 39 | 40 | target := NewTodoController(&test.MockTodoRepositoryGetTodosExist{}) 41 | target.GetTodos(w, r) 42 | 43 | if w.Code != 200 { 44 | t.Errorf("Response cod is %v", w.Code) 45 | } 46 | if w.Header().Get("Content-Type") != "application/json" { 47 | t.Errorf("Content-Type is %v", w.Header().Get("Content-Type")) 48 | } 49 | 50 | body := make([]byte, w.Body.Len()) 51 | w.Body.Read(body) 52 | var todosResponse dto.TodosResponse 53 | json.Unmarshal(body, &todosResponse.Todos) 54 | if len(todosResponse.Todos) != 2 { 55 | t.Errorf("Response is %v", todosResponse.Todos) 56 | } 57 | } 58 | 59 | func TestGetTodos_Error(t *testing.T) { 60 | w := httptest.NewRecorder() 61 | r := httptest.NewRequest("GET", "/todos/", nil) 62 | 63 | target := NewTodoController(&test.MockTodoRepositoryError{}) 64 | target.GetTodos(w, r) 65 | 66 | if w.Code != 500 { 67 | t.Errorf("Response cod is %v", w.Code) 68 | } 69 | if w.Header().Get("Content-Type") != "" { 70 | t.Errorf("Content-Type is %v", w.Header().Get("Content-Type")) 71 | } 72 | 73 | if w.Body.Len() != 0 { 74 | t.Errorf("body is %v", w.Body.Len()) 75 | } 76 | } 77 | 78 | func TestPostTodo_OK(t *testing.T) { 79 | json := strings.NewReader(`{"title":"test-title","content":"test-content"}`) 80 | w := httptest.NewRecorder() 81 | r := httptest.NewRequest("POST", "/todos/", json) 82 | 83 | target := NewTodoController(&test.MockTodoRepository{}) 84 | target.PostTodo(w, r) 85 | 86 | if w.Code != 201 { 87 | t.Errorf("Response cod is %v", w.Code) 88 | } 89 | if w.Header().Get("Location") != r.Host+r.URL.Path+"2" { 90 | t.Errorf("Location is %v", w.Header().Get("Location")) 91 | } 92 | } 93 | 94 | func TestPostTodo_Error(t *testing.T) { 95 | json := strings.NewReader(`{"title":"test-title","contents":"test-content"}`) 96 | w := httptest.NewRecorder() 97 | r := httptest.NewRequest("POST", "/todos/", json) 98 | 99 | target := NewTodoController(&test.MockTodoRepositoryError{}) 100 | target.PostTodo(w, r) 101 | 102 | if w.Code != 500 { 103 | t.Errorf("Response cod is %v", w.Code) 104 | } 105 | if w.Header().Get("Location") != "" { 106 | t.Errorf("Location is %v", w.Header().Get("Location")) 107 | } 108 | } 109 | 110 | func TestPutTodo_OK(t *testing.T) { 111 | json := strings.NewReader(`{"title":"test-title","contents":"test-content"}`) 112 | w := httptest.NewRecorder() 113 | r := httptest.NewRequest("PUT", "/todos/2", json) 114 | 115 | target := NewTodoController(&test.MockTodoRepository{}) 116 | target.PutTodo(w, r) 117 | 118 | if w.Code != 204 { 119 | t.Errorf("Response cod is %v", w.Code) 120 | } 121 | } 122 | 123 | func TestPutTodo_InvalidPath(t *testing.T) { 124 | w := httptest.NewRecorder() 125 | r := httptest.NewRequest("PUT", "/todos/", nil) 126 | 127 | target := NewTodoController(&test.MockTodoRepository{}) 128 | target.PutTodo(w, r) 129 | 130 | if w.Code != 400 { 131 | t.Errorf("Response cod is %v", w.Code) 132 | } 133 | } 134 | 135 | func TestPutTodo_Error(t *testing.T) { 136 | json := strings.NewReader(`{"title":"test-title","contents":"test-content"}`) 137 | w := httptest.NewRecorder() 138 | r := httptest.NewRequest("PUT", "/todos/2", json) 139 | 140 | target := NewTodoController(&test.MockTodoRepositoryError{}) 141 | target.PutTodo(w, r) 142 | 143 | if w.Code != 500 { 144 | t.Errorf("Response cod is %v", w.Code) 145 | } 146 | } 147 | 148 | func TestDeleteTodo_OK(t *testing.T) { 149 | w := httptest.NewRecorder() 150 | r := httptest.NewRequest("DELETE", "/todos/2", nil) 151 | 152 | target := NewTodoController(&test.MockTodoRepository{}) 153 | target.DeleteTodo(w, r) 154 | 155 | if w.Code != 204 { 156 | t.Errorf("Response cod is %v", w.Code) 157 | } 158 | } 159 | 160 | func TestDeleteTodo_InvalidPath(t *testing.T) { 161 | w := httptest.NewRecorder() 162 | r := httptest.NewRequest("DELETE", "/todos/", nil) 163 | 164 | target := NewTodoController(&test.MockTodoRepositoryError{}) 165 | target.DeleteTodo(w, r) 166 | 167 | if w.Code != 400 { 168 | t.Errorf("Response cod is %v", w.Code) 169 | } 170 | } 171 | 172 | func TestDeleteTodo_Error(t *testing.T) { 173 | w := httptest.NewRecorder() 174 | r := httptest.NewRequest("DELETE", "/todos/2", nil) 175 | 176 | target := NewTodoController(&test.MockTodoRepositoryError{}) 177 | target.DeleteTodo(w, r) 178 | 179 | if w.Code != 500 { 180 | t.Errorf("Response cod is %v", w.Code) 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/koga456/sample-api 2 | 3 | go 1.17 4 | 5 | require github.com/go-sql-driver/mysql v1.6.0 // indirect 6 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= 2 | github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= 3 | -------------------------------------------------------------------------------- /model/entity/todo_entity.go: -------------------------------------------------------------------------------- 1 | package entity 2 | 3 | type TodoEntity struct { 4 | Id int 5 | Title string 6 | Content string 7 | } 8 | -------------------------------------------------------------------------------- /model/repository/database.go: -------------------------------------------------------------------------------- 1 | package repository 2 | 3 | import ( 4 | "database/sql" 5 | "fmt" 6 | ) 7 | 8 | var Db *sql.DB 9 | 10 | func init() { 11 | var err error 12 | dataSourceName := fmt.Sprintf("%s:%s@tcp(%s)/%s?charset=utf8", 13 | "todo-app", "todo-password", "sample-api-db:3306", "todo", 14 | ) 15 | Db, err = sql.Open("mysql", dataSourceName) 16 | if err != nil { 17 | panic(err) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /model/repository/todo_repository.go: -------------------------------------------------------------------------------- 1 | package repository 2 | 3 | import ( 4 | "log" 5 | 6 | _ "github.com/go-sql-driver/mysql" 7 | 8 | "github.com/koga456/sample-api/model/entity" 9 | ) 10 | 11 | type TodoRepository interface { 12 | GetTodos() (todos []entity.TodoEntity, err error) 13 | InsertTodo(todo entity.TodoEntity) (id int, err error) 14 | UpdateTodo(todo entity.TodoEntity) (err error) 15 | DeleteTodo(id int) (err error) 16 | } 17 | 18 | type todoRepository struct { 19 | } 20 | 21 | func NewTodoRepository() TodoRepository { 22 | return &todoRepository{} 23 | } 24 | 25 | func (tr *todoRepository) GetTodos() (todos []entity.TodoEntity, err error) { 26 | todos = []entity.TodoEntity{} 27 | rows, err := Db. 28 | Query("SELECT id, title, content FROM todo ORDER BY id DESC") 29 | if err != nil { 30 | log.Print(err) 31 | return 32 | } 33 | 34 | for rows.Next() { 35 | todo := entity.TodoEntity{} 36 | err = rows.Scan(&todo.Id, &todo.Title, &todo.Content) 37 | if err != nil { 38 | log.Print(err) 39 | return 40 | } 41 | todos = append(todos, todo) 42 | } 43 | 44 | return 45 | } 46 | 47 | func (tr *todoRepository) InsertTodo(todo entity.TodoEntity) (id int, err error) { 48 | _, err = Db.Exec("INSERT INTO todo (title, content) VALUES (?, ?)", todo.Title, todo.Content) 49 | if err != nil { 50 | log.Print(err) 51 | return 52 | } 53 | err = Db.QueryRow("SELECT id FROM todo ORDER BY id DESC LIMIT 1").Scan(&id) 54 | return 55 | } 56 | 57 | func (tr *todoRepository) UpdateTodo(todo entity.TodoEntity) (err error) { 58 | _, err = Db.Exec("UPDATE todo SET title = ?, content = ? WHERE id = ?", todo.Title, todo.Content, todo.Id) 59 | return 60 | } 61 | 62 | func (tr *todoRepository) DeleteTodo(id int) (err error) { 63 | _, err = Db.Exec("DELETE FROM todo WHERE id = ?", id) 64 | return 65 | } 66 | -------------------------------------------------------------------------------- /test/mock.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "errors" 5 | "net/http" 6 | 7 | "github.com/koga456/sample-api/model/entity" 8 | ) 9 | 10 | type MockTodoController struct { 11 | } 12 | 13 | func (mtc *MockTodoController) GetTodos(w http.ResponseWriter, r *http.Request) { 14 | w.WriteHeader(200) 15 | } 16 | 17 | func (mtc *MockTodoController) PostTodo(w http.ResponseWriter, r *http.Request) { 18 | w.WriteHeader(201) 19 | } 20 | 21 | func (mtc *MockTodoController) PutTodo(w http.ResponseWriter, r *http.Request) { 22 | w.WriteHeader(204) 23 | } 24 | 25 | func (mtc *MockTodoController) DeleteTodo(w http.ResponseWriter, r *http.Request) { 26 | w.WriteHeader(204) 27 | } 28 | 29 | type MockTodoRepository struct { 30 | } 31 | 32 | func (mtr *MockTodoRepository) GetTodos() (todos []entity.TodoEntity, err error) { 33 | todos = []entity.TodoEntity{} 34 | return 35 | } 36 | 37 | func (mtr *MockTodoRepository) InsertTodo(todo entity.TodoEntity) (id int, err error) { 38 | id = 2 39 | return 40 | } 41 | 42 | func (mtr *MockTodoRepository) UpdateTodo(todo entity.TodoEntity) (err error) { 43 | return 44 | } 45 | 46 | func (mtr *MockTodoRepository) DeleteTodo(id int) (err error) { 47 | return 48 | } 49 | 50 | type MockTodoRepositoryGetTodosExist struct { 51 | } 52 | 53 | func (mtrgex *MockTodoRepositoryGetTodosExist) GetTodos() (todos []entity.TodoEntity, err error) { 54 | todos = []entity.TodoEntity{} 55 | todos = append(todos, entity.TodoEntity{Id: 1, Title: "title1", Content: "contents1"}) 56 | todos = append(todos, entity.TodoEntity{Id: 2, Title: "title2", Content: "contents2"}) 57 | return 58 | } 59 | 60 | func (mtrgex *MockTodoRepositoryGetTodosExist) InsertTodo(todo entity.TodoEntity) (id int, err error) { 61 | return 62 | } 63 | 64 | func (mtrgex *MockTodoRepositoryGetTodosExist) UpdateTodo(todo entity.TodoEntity) (err error) { 65 | return 66 | } 67 | 68 | func (mtrgex *MockTodoRepositoryGetTodosExist) DeleteTodo(id int) (err error) { 69 | return 70 | } 71 | 72 | type MockTodoRepositoryError struct { 73 | } 74 | 75 | func (mtrgtn *MockTodoRepositoryError) GetTodos() (todos []entity.TodoEntity, err error) { 76 | err = errors.New("unexpected error occurred") 77 | return 78 | } 79 | 80 | func (mtrgie *MockTodoRepositoryError) InsertTodo(todo entity.TodoEntity) (id int, err error) { 81 | err = errors.New("unexpected error occurred") 82 | return 83 | } 84 | 85 | func (mtrgue *MockTodoRepositoryError) UpdateTodo(todo entity.TodoEntity) (err error) { 86 | err = errors.New("unexpected error occurred") 87 | return 88 | } 89 | 90 | func (mtrgde *MockTodoRepositoryError) DeleteTodo(id int) (err error) { 91 | err = errors.New("unexpected error occurred") 92 | return 93 | } 94 | -------------------------------------------------------------------------------- /test_results/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koga456/sample-api/fa6f37e56e367ac8f4c24b07c824f9b22aa0dea4/test_results/.gitkeep --------------------------------------------------------------------------------