diff --git a/kadai4/int128/README.md b/kadai4/int128/README.md new file mode 100644 index 0000000..189c2df --- /dev/null +++ b/kadai4/int128/README.md @@ -0,0 +1,63 @@ +# kadai4 + +> - JSON形式でおみくじの結果を返す +> - 結果は毎回ランダムに変わるようにする +> - 正月(1/1-1/3)だけ大吉にする +> - ハンドラのテストを書いてみる + +## 実行例 + +``` +% go run main.go +2018/07/09 10:24:36 Listening on :8000 +2018/07/09 10:24:57 GET / +2018/07/09 10:25:09 GET /api/omikuji +2018/07/09 10:25:14 GET /api/omikuji +2018/07/09 10:25:16 GET /api/omikuji +``` + +``` +% curl -v http://localhost:8000/api/omikuji +> GET /api/omikuji HTTP/1.1 +> Host: localhost:8000 +> User-Agent: curl/7.54.0 +> Accept: */* +> +< HTTP/1.1 200 OK +< Content-Type: application/json +< Date: Mon, 09 Jul 2018 01:25:09 GMT +< Content-Length: 32 +< +{"description":"凶","value":1} +``` + +``` +% curl -v http://localhost:8000/api/omikuji +> GET /api/omikuji HTTP/1.1 +> Host: localhost:8000 +> User-Agent: curl/7.54.0 +> Accept: */* +> +< HTTP/1.1 200 OK +< Content-Type: application/json +< Date: Mon, 09 Jul 2018 01:25:16 GMT +< Content-Length: 35 +< +{"description":"中吉","value":3} +``` + +``` +% curl -v http://localhost:8000/ +> GET / HTTP/1.1 +> Host: localhost:8000 +> User-Agent: curl/7.54.0 +> Accept: */* +> +< HTTP/1.1 404 Not Found +< Content-Type: text/plain; charset=utf-8 +< X-Content-Type-Options: nosniff +< Date: Mon, 09 Jul 2018 01:24:57 GMT +< Content-Length: 19 +< +404 page not found +``` diff --git a/kadai4/int128/main.go b/kadai4/int128/main.go new file mode 100644 index 0000000..462a720 --- /dev/null +++ b/kadai4/int128/main.go @@ -0,0 +1,24 @@ +package main + +import ( + "log" + "net/http" + "os" + + "github.com/gopherdojo/dojo2/kadai4/int128/routes" +) + +func main() { + addr := ":8000" + if port := os.Getenv("PORT"); port != "" { + addr = ":" + port + } + s := http.Server{ + Addr: addr, + Handler: routes.New(), + } + log.Printf("Listening on %s", s.Addr) + if err := s.ListenAndServe(); err != nil { + log.Fatal(err) + } +} diff --git a/kadai4/int128/omikuji/service.go b/kadai4/int128/omikuji/service.go new file mode 100644 index 0000000..11b2d05 --- /dev/null +++ b/kadai4/int128/omikuji/service.go @@ -0,0 +1,57 @@ +package omikuji + +import ( + "math/rand" + "time" +) + +// Service provides omikuji. +type Service interface { + Hiku() Omikuji +} + +// New returns a new Service. +func New() Service { + return &DefaultService{ + random: rand.New(rand.NewSource(time.Now().UnixNano())), + } +} + +// DefaultService is a default implementation of Service. +type DefaultService struct { + random *rand.Rand // Random generator + time func() time.Time // Time provider (optional) +} + +// Hiku peforms おみくじを引く. +// If the current date is shogatsu, it always returns the DaiKichi. +func (s *DefaultService) Hiku() Omikuji { + if s.isShogatsu() { + return DaiKichi + } + n := s.random.Intn(6) + switch n { + default: + return Kyo + case 2, 3: + return ShoKichi + case 4: + return ChuKichi + case 5: + return DaiKichi + } +} + +func (s *DefaultService) isShogatsu() bool { + now := time.Now() + if s.time != nil { + now = s.time() + } + if now.Month() == time.January { + switch now.Day() { + case 1, 2, 3: + return true + } + } + return false +} diff --git a/kadai4/int128/omikuji/service_test.go b/kadai4/int128/omikuji/service_test.go new file mode 100644 index 0000000..30a686d --- /dev/null +++ b/kadai4/int128/omikuji/service_test.go @@ -0,0 +1,39 @@ +package omikuji + +import ( + "math/rand" + "testing" + "time" +) + +func TestDefaultService_IsShogatsu(t *testing.T) { + tokyoTime, err := time.LoadLocation("Asia/Tokyo") + if err != nil { + t.Fatal(err) + } + matrix := []struct { + now time.Time + expected bool + }{ + {time.Date(2017, 12, 31, 0, 0, 0, 0, tokyoTime), false}, + {time.Date(2017, 12, 31, 23, 59, 59, 0, tokyoTime), false}, + {time.Date(2018, 1, 1, 0, 0, 0, 0, tokyoTime), true}, + {time.Date(2018, 1, 2, 0, 0, 0, 0, tokyoTime), true}, + {time.Date(2018, 1, 3, 0, 0, 0, 0, tokyoTime), true}, + {time.Date(2018, 1, 3, 23, 59, 59, 0, tokyoTime), true}, + {time.Date(2018, 1, 4, 0, 0, 0, 0, tokyoTime), false}, + {time.Date(2018, 2, 1, 0, 0, 0, 0, tokyoTime), false}, + } + for _, m := range matrix { + t.Run(m.now.String(), func(t *testing.T) { + s := &DefaultService{ + random: rand.New(rand.NewSource(0)), + time: func() time.Time { return m.now }, + } + actual := s.isShogatsu() + if m.expected != actual { + t.Errorf("shogatsu wants %v but %v", m.expected, actual) + } + }) + } +} diff --git a/kadai4/int128/omikuji/types.go b/kadai4/int128/omikuji/types.go new file mode 100644 index 0000000..90ab5c0 --- /dev/null +++ b/kadai4/int128/omikuji/types.go @@ -0,0 +1,31 @@ +package omikuji + +// Omikuji represents a result of omikuji. +type Omikuji int + +const ( + _ Omikuji = iota + // Kyo means 凶. + Kyo + // ShoKichi means 小吉. + ShoKichi + // ChuKichi means 中吉. + ChuKichi + // DaiKichi means 大吉. + DaiKichi +) + +func (o Omikuji) String() string { + switch o { + case Kyo: + return "凶" + case ShoKichi: + return "小吉" + case ChuKichi: + return "中吉" + case DaiKichi: + return "大吉" + default: + return "" + } +} diff --git a/kadai4/int128/routes/omikuji.go b/kadai4/int128/routes/omikuji.go new file mode 100644 index 0000000..36a4ea8 --- /dev/null +++ b/kadai4/int128/routes/omikuji.go @@ -0,0 +1,36 @@ +package routes + +import ( + "encoding/json" + "net/http" + + "github.com/gopherdojo/dojo2/kadai4/int128/omikuji" +) + +// OmikujiHandler is a HTTP handler for GET omikuji +type OmikujiHandler struct { + service omikuji.Service +} + +// OmikujiGetResponse is a GET response of OmikujiHandler. +type OmikujiGetResponse struct { + Description string `json:"description"` + Value int `json:"value"` +} + +func (h *OmikujiHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { + defer req.Body.Close() + switch req.Method { + case "GET": + w.Header().Add("Content-Type", "application/json") + o := h.service.Hiku() + e := json.NewEncoder(w) + res := OmikujiGetResponse{o.String(), int(o)} + if err := e.Encode(res); err != nil { + http.Error(w, "Internal Server Error", http.StatusInternalServerError) + return + } + default: + http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed) + } +} diff --git a/kadai4/int128/routes/omikuji_test.go b/kadai4/int128/routes/omikuji_test.go new file mode 100644 index 0000000..3e168f9 --- /dev/null +++ b/kadai4/int128/routes/omikuji_test.go @@ -0,0 +1,39 @@ +package routes + +import ( + "encoding/json" + "net/http" + "net/http/httptest" + "testing" + + "github.com/gopherdojo/dojo2/kadai4/int128/omikuji" +) + +func TestOmikujiHandler_GET(t *testing.T) { + w := httptest.NewRecorder() + r := httptest.NewRequest("GET", "/api/omikuji", nil) + h := &OmikujiHandler{omikuji.New()} + h.ServeHTTP(w, r) + res := w.Result() + defer res.Body.Close() + if http.StatusOK != res.StatusCode { + t.Fatalf("res.StatusCode wants %d but %d", http.StatusOK, res.StatusCode) + } + d := json.NewDecoder(res.Body) + var json OmikujiGetResponse + if err := d.Decode(&json); err != nil { + t.Fatal(err) + } +} + +func TestOmikujiHandler_POST(t *testing.T) { + w := httptest.NewRecorder() + r := httptest.NewRequest("POST", "/api/omikuji", nil) + h := &OmikujiHandler{omikuji.New()} + h.ServeHTTP(w, r) + res := w.Result() + defer res.Body.Close() + if http.StatusMethodNotAllowed != res.StatusCode { + t.Fatalf("res.StatusCode wants %d but %d", http.StatusMethodNotAllowed, res.StatusCode) + } +} diff --git a/kadai4/int128/routes/routes.go b/kadai4/int128/routes/routes.go new file mode 100644 index 0000000..485837e --- /dev/null +++ b/kadai4/int128/routes/routes.go @@ -0,0 +1,22 @@ +package routes + +import ( + "log" + "net/http" + + "github.com/gopherdojo/dojo2/kadai4/int128/omikuji" +) + +// New returns a handler with application routes. +func New() http.Handler { + m := http.NewServeMux() + m.Handle("/api/omikuji", &OmikujiHandler{omikuji.New()}) + return withLogging(m) +} + +func withLogging(h http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + log.Printf("%s %s", r.Method, r.RequestURI) + h.ServeHTTP(w, r) + }) +}