diff --git a/kadai4/simady/README.md b/kadai4/simady/README.md new file mode 100644 index 0000000..a89ca31 --- /dev/null +++ b/kadai4/simady/README.md @@ -0,0 +1,17 @@ +# 課題4 + +## 内容 +おみくじAPI +- JSON形式でおみくじの結果を返す +- 正月 (1/1-1/3) だけ大吉にする +- ハンドラのテストを書いてみる + +## 実行方法 + +``` +go run cmd/main.go +``` + +## 所感 +アーキテクチャを少しだけ考慮してみました。 +Middleware等のテストをどのように実施するのがいいか悩みました。 diff --git a/kadai4/simady/cmd/main.go b/kadai4/simady/cmd/main.go new file mode 100644 index 0000000..85e908e --- /dev/null +++ b/kadai4/simady/cmd/main.go @@ -0,0 +1,9 @@ +package main + +import ( + "omikuji-app/pkg/api" +) + +func main() { + api.Serve(":8081") +} diff --git a/kadai4/simady/go.mod b/kadai4/simady/go.mod new file mode 100644 index 0000000..17d4090 --- /dev/null +++ b/kadai4/simady/go.mod @@ -0,0 +1,3 @@ +module omikuji-app + +go 1.12 diff --git a/kadai4/simady/pkg/api/api.go b/kadai4/simady/pkg/api/api.go new file mode 100644 index 0000000..f6b78c0 --- /dev/null +++ b/kadai4/simady/pkg/api/api.go @@ -0,0 +1,28 @@ +package api + +import ( + "fmt" + "net/http" + + "os" + + omikujiHandler "omikuji-app/pkg/api/app/handler/omikuji" + omikujiInteractor "omikuji-app/pkg/api/app/interactor/omikuji" + "omikuji-app/pkg/api/app/middleware" + "omikuji-app/pkg/api/app/presenter" + omikujiService "omikuji-app/pkg/api/domain/service/omikuji" +) + +func Serve(addr string) { + jsonPresenter := presenter.New() + service := omikujiService.New() + interactor := omikujiInteractor.New(jsonPresenter, service) + handler := omikujiHandler.New(interactor) + http.Handle("/", middleware.With(handler, middleware.ContextMiddleWare{}, middleware.ResponseHeaderMiddleWare{})) + http.Handle("/favicon.ico", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + http.Error(w, "Favicon is not set.", http.StatusNotFound) + })) + if err := http.ListenAndServe(addr, nil); err != nil { + fmt.Fprintf(os.Stderr, "Failed to serve. err = %v\n", err) + } +} diff --git a/kadai4/simady/pkg/api/app/handler/omikuji/handler.go b/kadai4/simady/pkg/api/app/handler/omikuji/handler.go new file mode 100644 index 0000000..9cd9a47 --- /dev/null +++ b/kadai4/simady/pkg/api/app/handler/omikuji/handler.go @@ -0,0 +1,27 @@ +package omikuji + +import ( + "fmt" + "net/http" + "os" + + interactor "omikuji-app/pkg/api/app/interactor/omikuji" +) + +type omikujiHandler struct { + omikujiInteractor interactor.OmikujiInteractor +} + +func New(i interactor.OmikujiInteractor) http.Handler { + return &omikujiHandler{omikujiInteractor: i} +} + +func (h *omikujiHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + res, err := h.omikujiInteractor.Draw(r.Context()) + if err != nil { + fmt.Fprintf(os.Stderr, "抽選に失敗しました. err = %v\n", err) + http.Error(w, "抽選に失敗しました.", http.StatusInternalServerError) + return + } + fmt.Fprintf(w, "%s", res) +} diff --git a/kadai4/simady/pkg/api/app/handler/omikuji/handler_test.go b/kadai4/simady/pkg/api/app/handler/omikuji/handler_test.go new file mode 100644 index 0000000..608b0af --- /dev/null +++ b/kadai4/simady/pkg/api/app/handler/omikuji/handler_test.go @@ -0,0 +1,103 @@ +package omikuji + +import ( + "context" + "io/ioutil" + "net/http" + "net/http/httptest" + "reflect" + "testing" + "time" + + interactor "omikuji-app/pkg/api/app/interactor/omikuji" + mockInteractor "omikuji-app/pkg/api/app/interactor/omikuji/mock" + "omikuji-app/pkg/api/ocontext" +) + +func TestNew(t *testing.T) { + type args struct { + i interactor.OmikujiInteractor + } + tests := []struct { + name string + args args + want http.Handler + }{ + { + name: "omikujiHandlerの生成", + args: args{ + i: mockInteractor.New(), + }, + want: &omikujiHandler{omikujiInteractor: mockInteractor.New()}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := New(tt.args.i); !reflect.DeepEqual(got, tt.want) { + t.Errorf("New() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_omikujiHandler_ServeHTTP(t *testing.T) { + type fields struct { + omikujiInteractor interactor.OmikujiInteractor + } + type args struct { + w http.ResponseWriter + r *http.Request + } + tests := []struct { + name string + fields fields + args args + want string + wantCode int + }{ + { + name: "リクエスト成功", + fields: fields{ + omikujiInteractor: mockInteractor.New(), + }, + args: args{ + w: httptest.NewRecorder(), + r: httptest.NewRequest("GET", "/", nil).WithContext(ocontext.SetAccessTime(context.Background(), time.Now())), + }, + want: "{\"id\":4,\"ruck\":\"吉\",\"message\":\"吉です!良い運勢ですね!\"}\n", + wantCode: http.StatusOK, + }, + { + name: "リクエスト失敗", + fields: fields{ + omikujiInteractor: mockInteractor.NewError(), + }, + args: args{ + w: httptest.NewRecorder(), + r: httptest.NewRequest("GET", "/", nil).WithContext(ocontext.SetAccessTime(context.Background(), time.Now())), + }, + want: "抽選に失敗しました.\n", + wantCode: http.StatusInternalServerError, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + h := &omikujiHandler{ + omikujiInteractor: tt.fields.omikujiInteractor, + } + h.ServeHTTP(tt.args.w, tt.args.r) + rw := tt.args.w.(*httptest.ResponseRecorder).Result() + defer rw.Body.Close() + if rw.StatusCode != tt.wantCode { + t.Errorf("unexpected status code: %v, want %v", rw.StatusCode, tt.wantCode) + } + b, err := ioutil.ReadAll(rw.Body) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + if s := string(b); s != tt.want { + t.Errorf("unexpected response: %v, want %v", s, tt.want) + } + }) + } +} diff --git a/kadai4/simady/pkg/api/app/interactor/omikuji/interactor.go b/kadai4/simady/pkg/api/app/interactor/omikuji/interactor.go new file mode 100644 index 0000000..1068dc4 --- /dev/null +++ b/kadai4/simady/pkg/api/app/interactor/omikuji/interactor.go @@ -0,0 +1,30 @@ +package interactor + +import ( + "context" + + "omikuji-app/pkg/api/app/presenter" + service "omikuji-app/pkg/api/domain/service/omikuji" +) + +type OmikujiInteractor interface { + Draw(ctx context.Context) (string, error) +} + +type omikujiInteractor struct { + presenter presenter.Presenter + omikujiService service.OmikujiService +} + +func New(p presenter.Presenter, s service.OmikujiService) OmikujiInteractor { + return &omikujiInteractor{presenter: p, omikujiService: s} +} + +func (i *omikujiInteractor) Draw(ctx context.Context) (string, error) { + rs := i.omikujiService.Draw(ctx) + output, err := i.presenter.Output(rs) + if err != nil { + return "", err + } + return output, nil +} diff --git a/kadai4/simady/pkg/api/app/interactor/omikuji/interactor_test.go b/kadai4/simady/pkg/api/app/interactor/omikuji/interactor_test.go new file mode 100644 index 0000000..846e35a --- /dev/null +++ b/kadai4/simady/pkg/api/app/interactor/omikuji/interactor_test.go @@ -0,0 +1,104 @@ +package interactor + +import ( + "context" + "fmt" + "reflect" + "testing" + + "omikuji-app/pkg/api/app/presenter" + mockPresenter "omikuji-app/pkg/api/app/presenter/mock" + entity "omikuji-app/pkg/api/domain/entity/omikuji" + service "omikuji-app/pkg/api/domain/service/omikuji" + mockService "omikuji-app/pkg/api/domain/service/omikuji/mock" +) + +func TestNew(t *testing.T) { + type args struct { + p presenter.Presenter + s service.OmikujiService + } + tests := []struct { + name string + args args + want OmikujiInteractor + }{ + { + name: "omikujiInteractorの生成", + args: args{ + p: mockPresenter.New(), + s: mockService.New(), + }, + want: &omikujiInteractor{presenter: mockPresenter.New(), omikujiService: mockService.New()}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := New(tt.args.p, tt.args.s); !reflect.DeepEqual(got, tt.want) { + t.Errorf("New() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_omikujiInteractor_Draw(t *testing.T) { + type fields struct { + presenter presenter.Presenter + omikujiService service.OmikujiService + } + type args struct { + ctx context.Context + } + tests := []struct { + name string + fields fields + args args + want string + wantErr bool + }{ + { + name: "正常終了", + fields: fields{ + presenter: mockPresenter.New(), + omikujiService: mockService.New(), + }, + args: args{ + ctx: context.Background(), + }, + want: fmt.Sprintf("output: %v", entity.OmikujiResult{ + ID: 4, + Ruck: "吉", + Message: "吉です!良い運勢ですね!", + }), + wantErr: false, + }, + { + name: "エラー発生", + fields: fields{ + presenter: mockPresenter.NewError(), + omikujiService: mockService.New(), + }, + args: args{ + ctx: context.Background(), + }, + want: "", + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + i := &omikujiInteractor{ + presenter: tt.fields.presenter, + omikujiService: tt.fields.omikujiService, + } + got, err := i.Draw(tt.args.ctx) + if (err != nil) != tt.wantErr { + t.Errorf("omikujiInteractor.Draw() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("omikujiInteractor.Draw() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/kadai4/simady/pkg/api/app/interactor/omikuji/mock/interactor_mock.go b/kadai4/simady/pkg/api/app/interactor/omikuji/mock/interactor_mock.go new file mode 100644 index 0000000..123eb88 --- /dev/null +++ b/kadai4/simady/pkg/api/app/interactor/omikuji/mock/interactor_mock.go @@ -0,0 +1,35 @@ +package mock + +import ( + "context" + + interactor "omikuji-app/pkg/api/app/interactor/omikuji" +) + +type mockInteractor struct { +} + +func New() interactor.OmikujiInteractor { + return &mockInteractor{} +} + +func (i *mockInteractor) Draw(ctx context.Context) (string, error) { + return "{\"id\":4,\"ruck\":\"吉\",\"message\":\"吉です!良い運勢ですね!\"}\n", nil +} + +type mockErrorInteractor struct { +} + +func NewError() interactor.OmikujiInteractor { + return &mockErrorInteractor{} +} + +func (i *mockErrorInteractor) Draw(ctx context.Context) (string, error) { + return "", &MockError{} +} + +type MockError struct{} + +func (e *MockError) Error() string { + return "mock interactor error." +} diff --git a/kadai4/simady/pkg/api/app/middleware/middleware.go b/kadai4/simady/pkg/api/app/middleware/middleware.go new file mode 100644 index 0000000..2cd0878 --- /dev/null +++ b/kadai4/simady/pkg/api/app/middleware/middleware.go @@ -0,0 +1,43 @@ +package middleware + +import ( + "context" + "net/http" + "time" + + "omikuji-app/pkg/api/ocontext" +) + +type MiddleWare interface { + ServeNext(h http.Handler) http.Handler +} + +func With(h http.Handler, ms ...MiddleWare) http.Handler { + for _, m := range ms { + h = m.ServeNext(h) + } + return h +} + +// ContextMiddleWare リクエスト起因のデータを格納する. +type ContextMiddleWare struct{} + +func (m ContextMiddleWare) ServeNext(h http.Handler) http.Handler { + f := func(w http.ResponseWriter, r *http.Request) { + ctx := ocontext.SetAccessTime(context.Background(), time.Now()) + r = r.WithContext(ctx) + h.ServeHTTP(w, r) + } + return http.HandlerFunc(f) +} + +// ResponseHeaderMiddleWare レスポンスヘッダーを設定する. +type ResponseHeaderMiddleWare struct{} + +func (m ResponseHeaderMiddleWare) ServeNext(h http.Handler) http.Handler { + f := func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json; charset=utf-8") + h.ServeHTTP(w, r) + } + return http.HandlerFunc(f) +} diff --git a/kadai4/simady/pkg/api/app/middleware/middleware_test.go b/kadai4/simady/pkg/api/app/middleware/middleware_test.go new file mode 100644 index 0000000..9e1304c --- /dev/null +++ b/kadai4/simady/pkg/api/app/middleware/middleware_test.go @@ -0,0 +1,87 @@ +package middleware + +import ( + "fmt" + "io/ioutil" + "net/http" + "net/http/httptest" + "testing" + "time" + + "omikuji-app/pkg/api/ocontext" +) + +func TestContextMiddleWare_ServeNext(t *testing.T) { + type args struct { + h http.Handler + } + tests := []struct { + name string + m ContextMiddleWare + args args + want string + }{ + { + name: "リクエスト起因データの設定", + m: ContextMiddleWare{}, + args: args{ + h: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // Contextにセットされたアクセス時刻をレスポンスに書き込む + fmt.Fprintf(w, "%s", ocontext.GetAccessTime(r.Context()).Format("2006-01-02 15:04:05")) + }), + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := tt.m.ServeNext(tt.args.h) + w := httptest.NewRecorder() + r := httptest.NewRequest("GET", "/", nil) + got.ServeHTTP(w, r) + + rw := w.Result() + defer rw.Body.Close() + b, err := ioutil.ReadAll(rw.Body) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + // 時刻としてパースできればOKとする + if _, err := time.Parse("2006-01-02 15:04:05", string(b)); err != nil { + t.Errorf("parse error: %v", err) + } + }) + } +} + +func TestResponseHeaderMiddleWare_ServeNext(t *testing.T) { + type args struct { + h http.Handler + } + tests := []struct { + name string + m ResponseHeaderMiddleWare + args args + want string + }{ + { + name: "Content-Typeの設定", + m: ResponseHeaderMiddleWare{}, + args: args{ + h: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}), + }, + want: "application/json; charset=utf-8", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := tt.m.ServeNext(tt.args.h) + w := httptest.NewRecorder() + r := httptest.NewRequest("GET", "/", nil) + got.ServeHTTP(w, r) + if c := w.Header().Get("Content-Type"); c != tt.want { + t.Errorf("Content-Type = %v, want %v", c, tt.want) + } + + }) + } +} diff --git a/kadai4/simady/pkg/api/app/presenter/mock/presenter_mock.go b/kadai4/simady/pkg/api/app/presenter/mock/presenter_mock.go new file mode 100644 index 0000000..eff8b73 --- /dev/null +++ b/kadai4/simady/pkg/api/app/presenter/mock/presenter_mock.go @@ -0,0 +1,35 @@ +package mock + +import ( + "fmt" + + "omikuji-app/pkg/api/app/presenter" +) + +type mockPresenter struct { +} + +func New() presenter.Presenter { + return &mockPresenter{} +} + +func (p *mockPresenter) Output(v interface{}) (string, error) { + return fmt.Sprintf("output: %v", v), nil +} + +type mockErrorPresenter struct { +} + +func NewError() presenter.Presenter { + return &mockErrorPresenter{} +} + +func (p *mockErrorPresenter) Output(v interface{}) (string, error) { + return "", &MockError{} +} + +type MockError struct{} + +func (e *MockError) Error() string { + return "mock presenter error." +} diff --git a/kadai4/simady/pkg/api/app/presenter/presenter.go b/kadai4/simady/pkg/api/app/presenter/presenter.go new file mode 100644 index 0000000..87d7aaa --- /dev/null +++ b/kadai4/simady/pkg/api/app/presenter/presenter.go @@ -0,0 +1,26 @@ +package presenter + +import ( + "bytes" + "encoding/json" +) + +type Presenter interface { + Output(v interface{}) (string, error) +} + +type presenter struct { +} + +func New() Presenter { + return &presenter{} +} + +func (p *presenter) Output(v interface{}) (string, error) { + var buf bytes.Buffer + enc := json.NewEncoder(&buf) + if err := enc.Encode(v); err != nil { + return "", err + } + return buf.String(), nil +} diff --git a/kadai4/simady/pkg/api/app/presenter/presenter_test.go b/kadai4/simady/pkg/api/app/presenter/presenter_test.go new file mode 100644 index 0000000..505f591 --- /dev/null +++ b/kadai4/simady/pkg/api/app/presenter/presenter_test.go @@ -0,0 +1,67 @@ +package presenter + +import ( + "reflect" + "testing" + + entity "omikuji-app/pkg/api/domain/entity/omikuji" +) + +func TestNew(t *testing.T) { + tests := []struct { + name string + want Presenter + }{ + { + name: "presenterの生成", + want: &presenter{}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := New(); !reflect.DeepEqual(got, tt.want) { + t.Errorf("New() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_presenter_Output(t *testing.T) { + type args struct { + v interface{} + } + tests := []struct { + name string + p *presenter + args args + want string + wantErr bool + }{ + { + name: "Jsonエンコード", + p: &presenter{}, + args: args{ + v: entity.OmikujiResult{ + ID: 1, + Ruck: "大吉", + Message: "メッセージ1", + }, + }, + want: "{\"id\":1,\"ruck\":\"大吉\",\"message\":\"メッセージ1\"}\n", + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + p := &presenter{} + got, err := p.Output(tt.args.v) + if (err != nil) != tt.wantErr { + t.Errorf("presenter.Output() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("presenter.Output() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/kadai4/simady/pkg/api/domain/entity/omikuji/entity.go b/kadai4/simady/pkg/api/domain/entity/omikuji/entity.go new file mode 100644 index 0000000..abbc8f8 --- /dev/null +++ b/kadai4/simady/pkg/api/domain/entity/omikuji/entity.go @@ -0,0 +1,38 @@ +package omikuji + +import ( + "log" + "math/rand" + "time" +) + +// おみくじ結果 +type OmikujiResult struct { + ID int32 `json:"id"` + Ruck string `json:"ruck"` + Message string `json:"message"` +} + +func init() { + rand.Seed(time.Now().UnixNano()) + log.Println("Seed initialized...") +} + +// おみくじ結果セット +type OmikujiResults []OmikujiResult + +// FindRandom おみくじ結果セットからランダムに一つを取得する. +func (rs OmikujiResults) FindRandom() OmikujiResult { + return rs[rand.Int31n(int32(len(rs)))] +} + +// ExtractByRuck おみくじ結果セットから特定のruckを抽出する. +func (rs OmikujiResults) ExtractByRuck(ruck string) OmikujiResults { + var ret OmikujiResults + for _, r := range rs { + if r.Ruck == ruck { + ret = append(ret, r) + } + } + return ret +} diff --git a/kadai4/simady/pkg/api/domain/entity/omikuji/entity_test.go b/kadai4/simady/pkg/api/domain/entity/omikuji/entity_test.go new file mode 100644 index 0000000..1b44d9c --- /dev/null +++ b/kadai4/simady/pkg/api/domain/entity/omikuji/entity_test.go @@ -0,0 +1,135 @@ +package omikuji + +import ( + "reflect" + "testing" +) + +func TestOmikujiResults_FindRandom(t *testing.T) { + tests := []struct { + name string + rs OmikujiResults + }{ + { + name: "ランダム選択", + rs: OmikujiResults{ + { + ID: 1, + Ruck: "大吉", + Message: "メッセージ1", + }, + { + ID: 2, + Ruck: "吉", + Message: "メッセージ2", + }, + { + ID: 3, + Ruck: "中吉", + Message: "メッセージ3", + }, + { + ID: 4, + Ruck: "小吉", + Message: "メッセージ4", + }, + { + ID: 5, + Ruck: "凶", + Message: "メッセージ5", + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + res := tt.rs.FindRandom() + // ランダム値で正否を判断できないのでログだけ出力 + t.Log(res) + }) + } +} + +func TestOmikujiResults_ExtractByRuck(t *testing.T) { + type args struct { + ruck string + } + tests := []struct { + name string + rs OmikujiResults + args args + want OmikujiResults + }{ + { + name: "小吉を抽出", + rs: OmikujiResults{ + { + ID: 1, + Ruck: "大吉", + Message: "メッセージ1", + }, + { + ID: 2, + Ruck: "吉", + Message: "メッセージ2", + }, + { + ID: 3, + Ruck: "小吉", + Message: "メッセージ3", + }, + { + ID: 4, + Ruck: "小吉", + Message: "メッセージ4", + }, + { + ID: 5, + Ruck: "凶", + Message: "メッセージ5", + }, + }, + args: args{ + ruck: "小吉", + }, + want: OmikujiResults{ + { + ID: 3, + Ruck: "小吉", + Message: "メッセージ3", + }, + { + ID: 4, + Ruck: "小吉", + Message: "メッセージ4", + }, + }, + }, + { + name: "一致するものがない", + rs: OmikujiResults{ + { + ID: 1, + Ruck: "大吉", + Message: "メッセージ1", + }, + { + ID: 2, + Ruck: "吉", + Message: "メッセージ2", + }, + }, + args: args{ + ruck: "凶", + }, + want: nil, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := tt.rs.ExtractByRuck(tt.args.ruck); !reflect.DeepEqual(got, tt.want) { + t.Errorf("OmikujiResults.ExtractByRuck() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/kadai4/simady/pkg/api/domain/service/omikuji/data.go b/kadai4/simady/pkg/api/domain/service/omikuji/data.go new file mode 100644 index 0000000..d92c164 --- /dev/null +++ b/kadai4/simady/pkg/api/domain/service/omikuji/data.go @@ -0,0 +1,56 @@ +package omikuji + +import entity "omikuji-app/pkg/api/domain/entity/omikuji" + +var omikujiResults = entity.OmikujiResults{ + { + ID: 1, + Ruck: "大吉", + Message: "おめでとうございます!大吉です!", + }, + { + ID: 2, + Ruck: "大吉", + Message: "大吉でした!絶好調です!", + }, + { + ID: 3, + Ruck: "吉", + Message: "吉です!かなりツイてます!", + }, + { + ID: 4, + Ruck: "吉", + Message: "吉です!良い運勢ですね!", + }, + { + ID: 5, + Ruck: "中吉", + Message: "中吉でした!いい感じです!", + }, + { + ID: 6, + Ruck: "中吉", + Message: "なかなかいいですね!中吉です!", + }, + { + ID: 7, + Ruck: "小吉", + Message: "小吉です!悪くないですね!", + }, + { + ID: 8, + Ruck: "小吉", + Message: "小吉です!少しツイてます!", + }, + { + ID: 9, + Ruck: "小吉", + Message: "小吉でした!!!", + }, + { + ID: 10, + Ruck: "凶", + Message: "凶でした!今日も一日頑張りましょう!", + }, +} diff --git a/kadai4/simady/pkg/api/domain/service/omikuji/mock/service_mock.go b/kadai4/simady/pkg/api/domain/service/omikuji/mock/service_mock.go new file mode 100644 index 0000000..a4bed51 --- /dev/null +++ b/kadai4/simady/pkg/api/domain/service/omikuji/mock/service_mock.go @@ -0,0 +1,26 @@ +package mock + +import ( + "context" + + entity "omikuji-app/pkg/api/domain/entity/omikuji" + "omikuji-app/pkg/api/domain/service/omikuji" +) + +// mockService OmikujiServiceのモック. +type mockService struct { +} + +// New モックを生成する. +func New() omikuji.OmikujiService { + return &mockService{} +} + +// Draw おみくじを引く. +func (s *mockService) Draw(ctx context.Context) entity.OmikujiResult { + return entity.OmikujiResult{ + ID: 4, + Ruck: "吉", + Message: "吉です!良い運勢ですね!", + } +} diff --git a/kadai4/simady/pkg/api/domain/service/omikuji/service.go b/kadai4/simady/pkg/api/domain/service/omikuji/service.go new file mode 100644 index 0000000..7f1b185 --- /dev/null +++ b/kadai4/simady/pkg/api/domain/service/omikuji/service.go @@ -0,0 +1,34 @@ +package omikuji + +import ( + "context" + + entity "omikuji-app/pkg/api/domain/entity/omikuji" + "omikuji-app/pkg/api/ocontext" +) + +const dateFormat = "1/2" + +var daikichiOnlyDates = map[string]struct{}{"1/1": {}, "1/2": {}, "1/3": {}} + +type OmikujiService interface { + Draw(ctx context.Context) entity.OmikujiResult +} + +type omikujiService struct { +} + +func New() OmikujiService { + return &omikujiService{} +} + +// Draw おみくじを引く. +func (s *omikujiService) Draw(ctx context.Context) entity.OmikujiResult { + t := ocontext.GetAccessTime(ctx) + rs := omikujiResults + date := t.Format(dateFormat) + if _, exists := daikichiOnlyDates[date]; exists { + rs = rs.ExtractByRuck("大吉") + } + return rs.FindRandom() +} diff --git a/kadai4/simady/pkg/api/domain/service/omikuji/service_test.go b/kadai4/simady/pkg/api/domain/service/omikuji/service_test.go new file mode 100644 index 0000000..4709380 --- /dev/null +++ b/kadai4/simady/pkg/api/domain/service/omikuji/service_test.go @@ -0,0 +1,65 @@ +package omikuji + +import ( + "context" + "reflect" + "testing" + "time" + + "omikuji-app/pkg/api/ocontext" +) + +func TestNew(t *testing.T) { + tests := []struct { + name string + want OmikujiService + }{ + { + name: "omikujiServiceの生成", + want: &omikujiService{}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := New(); !reflect.DeepEqual(got, tt.want) { + t.Errorf("New() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_omikujiService_Draw(t *testing.T) { + type args struct { + ctx context.Context + } + tests := []struct { + name string + s *omikujiService + args args + want string + }{ + { + name: "正月以外のおみくじ", + s: &omikujiService{}, + args: args{ + ctx: ocontext.SetAccessTime(context.Background(), time.Date(2000, 1, 4, 3, 4, 5, 6, time.Local)), + }, + }, + { + name: "1/2のおみくじ", + s: &omikujiService{}, + args: args{ + ctx: ocontext.SetAccessTime(context.Background(), time.Date(2000, 1, 2, 3, 4, 5, 6, time.Local)), + }, + want: "大吉", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := tt.s.Draw(tt.args.ctx) + if tt.want != "" && !reflect.DeepEqual(got.Ruck, tt.want) { + t.Errorf("omikujiService.Draw() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/kadai4/simady/pkg/api/ocontext/context.go b/kadai4/simady/pkg/api/ocontext/context.go new file mode 100644 index 0000000..5a78e92 --- /dev/null +++ b/kadai4/simady/pkg/api/ocontext/context.go @@ -0,0 +1,18 @@ +package ocontext + +import ( + "context" + "time" +) + +type accessTimeKey struct{} + +// SetAccessTime contextにアクセス日時を設定する. +func SetAccessTime(ctx context.Context, time time.Time) context.Context { + return context.WithValue(ctx, accessTimeKey{}, time) +} + +// GetAccessTime contextからアクセス日時を取得する. +func GetAccessTime(ctx context.Context) time.Time { + return ctx.Value(accessTimeKey{}).(time.Time) +} diff --git a/kadai4/simady/pkg/api/ocontext/context_test.go b/kadai4/simady/pkg/api/ocontext/context_test.go new file mode 100644 index 0000000..324b70d --- /dev/null +++ b/kadai4/simady/pkg/api/ocontext/context_test.go @@ -0,0 +1,62 @@ +package ocontext + +import ( + "context" + "reflect" + "testing" + "time" +) + +func TestSetAccessTime(t *testing.T) { + type args struct { + ctx context.Context + time time.Time + } + tests := []struct { + name string + args args + want context.Context + }{ + { + name: "アクセス時刻の設定", + args: args{ + ctx: context.Background(), + time: time.Date(2000, 1, 2, 3, 4, 5, 6, time.Local), + }, + want: context.WithValue(context.Background(), accessTimeKey{}, time.Date(2000, 1, 2, 3, 4, 5, 6, time.Local)), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := SetAccessTime(tt.args.ctx, tt.args.time); !reflect.DeepEqual(got, tt.want) { + t.Errorf("SetAccessTime() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestGetAccessTime(t *testing.T) { + type args struct { + ctx context.Context + } + tests := []struct { + name string + args args + want time.Time + }{ + { + name: "アクセス時刻の取得", + args: args{ + ctx: context.WithValue(context.Background(), accessTimeKey{}, time.Date(2000, 1, 2, 3, 4, 5, 6, time.Local)), + }, + want: time.Date(2000, 1, 2, 3, 4, 5, 6, time.Local), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := GetAccessTime(tt.args.ctx); !reflect.DeepEqual(got, tt.want) { + t.Errorf("GetAccessTime() = %v, want %v", got, tt.want) + } + }) + } +}