From 07237b305826c095ced9bdbf86fd1cff84cea0d2 Mon Sep 17 00:00:00 2001 From: Go Takagi Date: Sun, 16 Sep 2018 15:52:35 +0900 Subject: [PATCH 1/6] Draw random fortune --- kadai4/shimastripe/cli.go | 35 +++++++++++++++++++ kadai4/shimastripe/cmd/kadai4/main.go | 12 +++++++ kadai4/shimastripe/omikuji.go | 50 +++++++++++++++++++++++++++ 3 files changed, 97 insertions(+) create mode 100644 kadai4/shimastripe/cli.go create mode 100644 kadai4/shimastripe/cmd/kadai4/main.go create mode 100644 kadai4/shimastripe/omikuji.go diff --git a/kadai4/shimastripe/cli.go b/kadai4/shimastripe/cli.go new file mode 100644 index 0000000..404c322 --- /dev/null +++ b/kadai4/shimastripe/cli.go @@ -0,0 +1,35 @@ +package shimastripe + +import ( + "encoding/json" + "log" + "math/rand" + "net/http" + "time" +) + +const success = iota + +type CLI struct { +} + +func init() { + rand.Seed(time.Now().Unix()) +} + +func handler(w http.ResponseWriter, req *http.Request) { + w.Header().Set("Content-Type", "application/json; charset=utf-8") + w.Header().Set("X-Content-Type-Options", "nosniff") + respBody := &FortuneRepository{Fortune: DrawRandomly()} + + if err := json.NewEncoder(w).Encode(respBody); err != nil { + log.Printf("Encode error: %v\n", err) + http.Error(w, "Internal server error.", http.StatusInternalServerError) + } +} + +func (c *CLI) Run(args []string) int { + http.HandleFunc("/", handler) + http.ListenAndServe(":8080", nil) + return success +} diff --git a/kadai4/shimastripe/cmd/kadai4/main.go b/kadai4/shimastripe/cmd/kadai4/main.go new file mode 100644 index 0000000..6c534f9 --- /dev/null +++ b/kadai4/shimastripe/cmd/kadai4/main.go @@ -0,0 +1,12 @@ +package main + +import ( + "os" + + "github.com/gopherdojo/dojo3/kadai4/shimastripe" +) + +func main() { + cli := &shimastripe.CLI{} + os.Exit(cli.Run(os.Args)) +} diff --git a/kadai4/shimastripe/omikuji.go b/kadai4/shimastripe/omikuji.go new file mode 100644 index 0000000..c8892d6 --- /dev/null +++ b/kadai4/shimastripe/omikuji.go @@ -0,0 +1,50 @@ +package shimastripe + +import "math/rand" + +type FortuneElement int +type FortuneRepository struct { + Fortune FortuneElement `json:"fortune"` +} + +const ( + daikichi FortuneElement = iota + chukichi + shokichi + suekichi + kichi + kyo + daikyo + threshold // use FortuneElement length +) + +// Behave Stringer interface +func (oe FortuneElement) String() string { + switch oe { + case daikichi: + return "大吉" + case chukichi: + return "中吉" + case shokichi: + return "小吉" + case suekichi: + return "末吉" + case kichi: + return "吉" + case kyo: + return "凶" + case daikyo: + return "大凶" + default: + return "強運" + } +} + +func (oe FortuneElement) MarshalJSON() ([]byte, error) { + return []byte(`"` + oe.String() + `"`), nil +} + +// Draw a fortune randomly +func DrawRandomly() FortuneElement { + return FortuneElement(rand.Intn(int(threshold))) +} From 5b427dd1f33243393393b053fe9f8dfa6bf266e9 Mon Sep 17 00:00:00 2001 From: Go Takagi Date: Sun, 16 Sep 2018 16:01:42 +0900 Subject: [PATCH 2/6] Rename --- kadai4/shimastripe/{omikuji.go => fortune.go} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename kadai4/shimastripe/{omikuji.go => fortune.go} (100%) diff --git a/kadai4/shimastripe/omikuji.go b/kadai4/shimastripe/fortune.go similarity index 100% rename from kadai4/shimastripe/omikuji.go rename to kadai4/shimastripe/fortune.go From 4540ee0b63da1bcba8829f29feecbde197110b5e Mon Sep 17 00:00:00 2001 From: Go Takagi Date: Sun, 16 Sep 2018 17:49:37 +0900 Subject: [PATCH 3/6] Use mock clock --- kadai4/shimastripe/cli.go | 14 +++++++++----- kadai4/shimastripe/clock.go | 15 +++++++++++++++ kadai4/shimastripe/cmd/kadai4/main.go | 5 ++++- kadai4/shimastripe/holiday.go | 5 +++++ 4 files changed, 33 insertions(+), 6 deletions(-) create mode 100644 kadai4/shimastripe/clock.go create mode 100644 kadai4/shimastripe/holiday.go diff --git a/kadai4/shimastripe/cli.go b/kadai4/shimastripe/cli.go index 404c322..91ab11d 100644 --- a/kadai4/shimastripe/cli.go +++ b/kadai4/shimastripe/cli.go @@ -5,19 +5,21 @@ import ( "log" "math/rand" "net/http" - "time" ) const success = iota type CLI struct { + Clock Clock } -func init() { - rand.Seed(time.Now().Unix()) +// Generate a seed only once +func (c *CLI) generateSeed() { + rand.Seed(c.Clock.Now().Unix()) } -func handler(w http.ResponseWriter, req *http.Request) { +// handler +func (c *CLI) handler(w http.ResponseWriter, req *http.Request) { w.Header().Set("Content-Type", "application/json; charset=utf-8") w.Header().Set("X-Content-Type-Options", "nosniff") respBody := &FortuneRepository{Fortune: DrawRandomly()} @@ -28,8 +30,10 @@ func handler(w http.ResponseWriter, req *http.Request) { } } +// Run a server func (c *CLI) Run(args []string) int { - http.HandleFunc("/", handler) + c.generateSeed() + http.HandleFunc("/", c.handler) http.ListenAndServe(":8080", nil) return success } diff --git a/kadai4/shimastripe/clock.go b/kadai4/shimastripe/clock.go new file mode 100644 index 0000000..5fecdc2 --- /dev/null +++ b/kadai4/shimastripe/clock.go @@ -0,0 +1,15 @@ +package shimastripe + +import ( + "time" +) + +type Clock interface { + Now() time.Time +} + +type ClockFunc func() time.Time + +func (f ClockFunc) Now() time.Time { + return f() +} diff --git a/kadai4/shimastripe/cmd/kadai4/main.go b/kadai4/shimastripe/cmd/kadai4/main.go index 6c534f9..85e5493 100644 --- a/kadai4/shimastripe/cmd/kadai4/main.go +++ b/kadai4/shimastripe/cmd/kadai4/main.go @@ -2,11 +2,14 @@ package main import ( "os" + "time" "github.com/gopherdojo/dojo3/kadai4/shimastripe" ) func main() { - cli := &shimastripe.CLI{} + cli := &shimastripe.CLI{Clock: shimastripe.ClockFunc(func() time.Time { + return time.Now() + })} os.Exit(cli.Run(os.Args)) } diff --git a/kadai4/shimastripe/holiday.go b/kadai4/shimastripe/holiday.go new file mode 100644 index 0000000..05df8dd --- /dev/null +++ b/kadai4/shimastripe/holiday.go @@ -0,0 +1,5 @@ +package shimastripe + +// func IsNewYear(timezone time.Location)) bool { + +// } From a692664ee1187654e5a9b92cd259e8515169e214 Mon Sep 17 00:00:00 2001 From: Go Takagi Date: Sun, 16 Sep 2018 17:56:11 +0900 Subject: [PATCH 4/6] Check a new year --- kadai4/shimastripe/cli.go | 16 +++++++++++++++- kadai4/shimastripe/holiday.go | 5 ----- 2 files changed, 15 insertions(+), 6 deletions(-) delete mode 100644 kadai4/shimastripe/holiday.go diff --git a/kadai4/shimastripe/cli.go b/kadai4/shimastripe/cli.go index 91ab11d..753f7e3 100644 --- a/kadai4/shimastripe/cli.go +++ b/kadai4/shimastripe/cli.go @@ -18,11 +18,25 @@ func (c *CLI) generateSeed() { rand.Seed(c.Clock.Now().Unix()) } +func (c *CLI) IsNewYear() bool { + _, m, d := c.Clock.Now().Date() + if m == 1 && d <= 3 { + return true + } + return false +} + // handler func (c *CLI) handler(w http.ResponseWriter, req *http.Request) { w.Header().Set("Content-Type", "application/json; charset=utf-8") w.Header().Set("X-Content-Type-Options", "nosniff") - respBody := &FortuneRepository{Fortune: DrawRandomly()} + var respBody *FortuneRepository + + if c.IsNewYear() { + respBody = &FortuneRepository{Fortune: daikichi} + } else { + respBody = &FortuneRepository{Fortune: DrawRandomly()} + } if err := json.NewEncoder(w).Encode(respBody); err != nil { log.Printf("Encode error: %v\n", err) diff --git a/kadai4/shimastripe/holiday.go b/kadai4/shimastripe/holiday.go deleted file mode 100644 index 05df8dd..0000000 --- a/kadai4/shimastripe/holiday.go +++ /dev/null @@ -1,5 +0,0 @@ -package shimastripe - -// func IsNewYear(timezone time.Location)) bool { - -// } From 424343ba164019c6aff3fe87e43a3daa1b0842fb Mon Sep 17 00:00:00 2001 From: Go Takagi Date: Sun, 16 Sep 2018 22:10:34 +0900 Subject: [PATCH 5/6] Write test --- kadai4/shimastripe/cli.go | 2 +- kadai4/shimastripe/cli_test.go | 112 +++++++++++++++++++++++++++++++++ 2 files changed, 113 insertions(+), 1 deletion(-) create mode 100644 kadai4/shimastripe/cli_test.go diff --git a/kadai4/shimastripe/cli.go b/kadai4/shimastripe/cli.go index 753f7e3..fb3d22c 100644 --- a/kadai4/shimastripe/cli.go +++ b/kadai4/shimastripe/cli.go @@ -40,7 +40,7 @@ func (c *CLI) handler(w http.ResponseWriter, req *http.Request) { if err := json.NewEncoder(w).Encode(respBody); err != nil { log.Printf("Encode error: %v\n", err) - http.Error(w, "Internal server error.", http.StatusInternalServerError) + http.Error(w, "{\"result\":\"Internal server error\"}\n", http.StatusInternalServerError) } } diff --git a/kadai4/shimastripe/cli_test.go b/kadai4/shimastripe/cli_test.go new file mode 100644 index 0000000..8c7bdbc --- /dev/null +++ b/kadai4/shimastripe/cli_test.go @@ -0,0 +1,112 @@ +package shimastripe + +import ( + "encoding/json" + "net/http" + "net/http/httptest" + "os" + "testing" + "time" +) + +var fortuneList []string + +func TestMain(m *testing.M) { + setup() + ret := m.Run() + os.Exit(ret) +} + +func setup() { + for index := 0; index < int(threshold); index++ { + fortuneList = append(fortuneList, FortuneElement(index).String()) + } +} + +// Test handler. This test is flaky. (choose fortune element randomly) +func TestHandlerRandomly(t *testing.T) { + cli := &CLI{Clock: ClockFunc(func() time.Time { + return time.Now() + })} + w := httptest.NewRecorder() + r := httptest.NewRequest("GET", "/", nil) + cli.handler(w, r) + rw := w.Result() + defer rw.Body.Close() + + if rw.StatusCode != http.StatusOK { + t.Error("unexpected status code\n") + } + + m := struct { + Fortune string `json:"fortune"` + }{} + + if err := json.NewDecoder(rw.Body).Decode(&m); err != nil { + t.Error("Decode error\n") + } + + if !contain(fortuneList, m.Fortune) { + t.Errorf("unexpected fortune element: %v", m.Fortune) + } +} + +func TestHandlerWhenNewYear(t *testing.T) { + cases := []struct { + clock Clock + answer string + }{ + {clock: mockClock(t, "2018/01/01"), answer: "大吉"}, + {clock: mockClock(t, "2018/01/02"), answer: "大吉"}, + {clock: mockClock(t, "2018/01/03"), answer: "大吉"}, + } + + for _, c := range cases { + t.Run("NewYearCase", func(t *testing.T) { + t.Helper() + cli := &CLI{Clock: c.clock} + w := httptest.NewRecorder() + r := httptest.NewRequest("GET", "/", nil) + cli.handler(w, r) + rw := w.Result() + defer rw.Body.Close() + + if rw.StatusCode != http.StatusOK { + t.Error("unexpected status code\n") + } + + m := struct { + Fortune string `json:"fortune"` + }{} + + if err := json.NewDecoder(rw.Body).Decode(&m); err != nil { + t.Error("Decode error\n") + } + + if m.Fortune != c.answer { + t.Errorf("unexpected fortune element: %v, expected: %v", m.Fortune, c.answer) + } + }) + } +} + +func contain(list []string, elm string) bool { + for _, l := range list { + if l == elm { + return true + } + } + return false +} + +func mockClock(t *testing.T, v string) Clock { + t.Helper() + now, err := time.Parse("2006/01/02", v) + if err != nil { + t.Fatal("unexpected error:", err) + } + + return ClockFunc(func() time.Time { + return now + }) +} From 07c908dedd23ddf817628d3b8e98c069a9dba6a8 Mon Sep 17 00:00:00 2001 From: Go Takagi Date: Sun, 16 Sep 2018 22:25:27 +0900 Subject: [PATCH 6/6] Write readme --- kadai4/shimastripe/README.md | 41 ++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 kadai4/shimastripe/README.md diff --git a/kadai4/shimastripe/README.md b/kadai4/shimastripe/README.md new file mode 100644 index 0000000..4ecbf49 --- /dev/null +++ b/kadai4/shimastripe/README.md @@ -0,0 +1,41 @@ +# おみくじAPIを作ってみよう + +## 仕様 + +- JSON形式でおみくじ􏰁結果を返す +- 正月(1/1-1/3)だけ大吉にする +- ハンドラ􏰁テストを書いてみる + +## 実行方法 + + ```bash + $ go run cmd/kadai4/main.go + $ curl 'http://localhost:8080' + + # => {"fortune":"大吉"} + ``` + +## Test + + ```bash + $ go test -v + + === RUN TestHandlerRandomly + --- PASS: TestHandlerRandomly (0.00s) + === RUN TestHandlerWhenNewYear + === RUN TestHandlerWhenNewYear/NewYearCase + === RUN TestHandlerWhenNewYear/NewYearCase#01 + === RUN TestHandlerWhenNewYear/NewYearCase#02 + --- PASS: TestHandlerWhenNewYear (0.00s) + --- PASS: TestHandlerWhenNewYear/NewYearCase (0.00s) + --- PASS: TestHandlerWhenNewYear/NewYearCase#01 (0.00s) + --- PASS: TestHandlerWhenNewYear/NewYearCase#02 (0.00s) + PASS + ok github.com/gopherdojo/dojo3/kadai4/shimastripe 0.018s +``` + +## Detail + +- 時間は講義で受けたClock interfaceを用意して抽象化 +- ハンドラ側のテストを用意 + - ランダムで決定する要素と決定しない要素を分けて書いた。ランダム要素側は不安定なので実質フレークテスト \ No newline at end of file