diff --git a/kadai4/misonog/.gitignore b/kadai4/misonog/.gitignore new file mode 100644 index 00000000..fe037cc6 --- /dev/null +++ b/kadai4/misonog/.gitignore @@ -0,0 +1,16 @@ +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib +/omikuji-server + +# Test binary, built with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Dependency directories (remove the comment below to include it) +# vendor/ diff --git a/kadai4/misonog/Makefile b/kadai4/misonog/Makefile new file mode 100644 index 00000000..63066b83 --- /dev/null +++ b/kadai4/misonog/Makefile @@ -0,0 +1,9 @@ +BINARY_NAME=omikuji-server + +all: test build + +build: + go build -o $(BINARY_NAME) + +test: + go test -v ./... diff --git a/kadai4/misonog/README.md b/kadai4/misonog/README.md new file mode 100644 index 00000000..59ed0ab0 --- /dev/null +++ b/kadai4/misonog/README.md @@ -0,0 +1,43 @@ +# おみくじ API + +## 仕様 + +- JSON 形式でおみくじの結果を返す +- 正月(1/1-1/3)だけ大吉にする +- ハンドラのテストを書いてみる + +## 利用方法 + +### setup + +```shell +$ make # テスト & ビルド +``` + +### サーバーの起動 + +```shell +$ ./omikuji-server +``` + +### おみくじを引く + +```shell +$ curl "http://127.0.0.1:8080/" +> {"result":"小吉"} +$ curl "http://127.0.0.1:8080/?date=2021-01-01" +> {"result":"大吉"} +``` + +## ディレクトリ構造 + +```shell +. +├── Makefile +├── README.md +├── go.mod +├── main.go +├── omikuji-server +├── omikuji.go +└── omikuji_test.go +``` diff --git a/kadai4/misonog/go.mod b/kadai4/misonog/go.mod new file mode 100644 index 00000000..e63fff4a --- /dev/null +++ b/kadai4/misonog/go.mod @@ -0,0 +1,3 @@ +module github.com/misonog/gopherdojo-studyroom/kadai4/misonog + +go 1.16 diff --git a/kadai4/misonog/main.go b/kadai4/misonog/main.go new file mode 100644 index 00000000..dd42b732 --- /dev/null +++ b/kadai4/misonog/main.go @@ -0,0 +1,15 @@ +package main + +import ( + "log" + "net/http" +) + +func main() { + http.HandleFunc("/", handler) + + err := http.ListenAndServe(":8080", nil) + if err != nil { + log.Fatal(err) + } +} diff --git a/kadai4/misonog/omikuji.go b/kadai4/misonog/omikuji.go new file mode 100644 index 00000000..a6367b38 --- /dev/null +++ b/kadai4/misonog/omikuji.go @@ -0,0 +1,73 @@ +package main + +import ( + "encoding/json" + "log" + "math/rand" + "net/http" + "time" +) + +const ( + timeForm = "2006-1-2" + daikichi = "大吉" +) + +var omikujiResults = []string{"吉", "小吉", "凶"} + +type omikujiResult struct { + Result string `json:"result"` +} + +type omikuji struct { + result string + date time.Time +} + +func handler(w http.ResponseWriter, r *http.Request) { + var o omikuji + + dateValue := r.FormValue("date") + if dateValue != "" { + date, err := time.Parse(timeForm, dateValue) + if err != nil { + log.Fatal(err) + } + o = omikuji{date: date} + } else { + o = omikuji{} + } + + o.Draw() + data := &omikujiResult{Result: o.result} + + w.Header().Set("Content-Type", "application/json; charset=utf-8") + if err := json.NewEncoder(w).Encode(data); err != nil { + log.Fatal(err) + } +} + +func (o *omikuji) Draw() { + if isShogatsu(o.date) { + o.result = daikichi + } else { + rand.Seed(time.Now().UnixNano()) + i := rand.Intn(len(omikujiResults)) + o.result = omikujiResults[i] + } +} + +func isShogatsu(t time.Time) bool { + // 0001/1/1は正月と判定できない + if t.IsZero() { + return false + } + + _, month, date := t.Date() + if month == time.January { + if date == 1 || date == 2 || date == 3 { + return true + } + } + return false +} diff --git a/kadai4/misonog/omikuji_test.go b/kadai4/misonog/omikuji_test.go new file mode 100644 index 00000000..4cf0425c --- /dev/null +++ b/kadai4/misonog/omikuji_test.go @@ -0,0 +1,70 @@ +package main + +import ( + "encoding/json" + "net/http" + "net/http/httptest" + "testing" + "time" +) + +func TestHandler(t *testing.T) { + cases := []struct { + name string + date string + fixedResult bool + expected string + }{ + {name: "no date", date: ""}, + {name: "12/31", date: "2020-12-31"}, + {name: "shogatsu", date: "2021-01-01", fixedResult: true, expected: "大吉"}, + {name: "1/4", date: "2021-01-04"}, + } + + for _, c := range cases { + c := c + t.Run(c.name, func(t *testing.T) { + w := httptest.NewRecorder() + r := httptest.NewRequest("GET", "/?date="+c.date, nil) + handler(w, r) + rw := w.Result() + defer rw.Body.Close() + if rw.StatusCode != http.StatusOK { + t.Fatal("unexpected status code") + } + + var res omikujiResult + dec := json.NewDecoder(rw.Body) + if err := dec.Decode(&res); err != nil { + t.Fatal(err) + } + if c.fixedResult && c.expected != res.Result { + t.Errorf("want omikujiResult.Result = %v, got %v", + res.Result, c.expected) + } + }) + } +} + +func TestIsShogatsu(t *testing.T) { + cases := []struct { + name string + input time.Time + expected bool + }{ + {name: "nil", expected: false}, + {name: "shogatsu 1/1", input: time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC), expected: true}, + {name: "shogatsu 1/2", input: time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC), expected: true}, + {name: "not shogatsu", input: time.Date(2021, 1, 4, 0, 0, 0, 0, time.UTC), expected: false}, + } + + for _, c := range cases { + c := c + t.Run(c.name, func(t *testing.T) { + if actual := isShogatsu(c.input); actual != c.expected { + t.Errorf("want isShogatsu(%v) = %v, got %v", + c.input, c.expected, actual) + } + }) + } +}