diff --git a/kadai4/zaneli/omikuji/README.md b/kadai4/zaneli/omikuji/README.md new file mode 100644 index 0000000..1856fe1 --- /dev/null +++ b/kadai4/zaneli/omikuji/README.md @@ -0,0 +1,41 @@ +## 課題4 + +おみくじAPI + +### 使い方 + +ポートを指定しないで起動する。(デフォルト8080ポートが使用される。) + +```sh +go run main.go +``` + +ポートを指定して起動する。 + +```sh +go run main.go 8081 +``` + +アクセスする。 + +``` +> curl --dump-header - "http://localhost:8080/" +HTTP/1.1 200 OK +Content-Type: application/json; charset=UTF-8 +Date: Sat, 02 Jun 2018 17:52:03 GMT +Content-Length: 52 + +{"result":"吉","date":"2018-06-03T02:52:03+09:00"} +``` + +テストを実行する。 + +``` +> go test ./omikuji +ok _/Users/zaneli/ws/dojo1/kadai4/zaneli/omikuji/omikuji 0.026s +``` + +``` +> go test ./omikuji --cover +ok _/Users/zaneli/ws/dojo1/kadai4/zaneli/omikuji/omikuji 0.026s coverage: 76.2% of statements +``` diff --git a/kadai4/zaneli/omikuji/main.go b/kadai4/zaneli/omikuji/main.go new file mode 100644 index 0000000..d529b3a --- /dev/null +++ b/kadai4/zaneli/omikuji/main.go @@ -0,0 +1,26 @@ +package main + +import ( + "fmt" + "log" + "net/http" + "os" + "strconv" + + "./omikuji" +) + +func main() { + port := 8080 + if len(os.Args) > 1 { + p, err := strconv.Atoi(os.Args[1]) + if err != nil { + log.Fatal(err) + } + port = p + } + + mux := http.NewServeMux() + mux.Handle("/", omikuji.AddCurrentDateTime(http.HandlerFunc(omikuji.Handler))) + http.ListenAndServe(fmt.Sprintf(":%d", port), mux) +} diff --git a/kadai4/zaneli/omikuji/omikuji/context.go b/kadai4/zaneli/omikuji/omikuji/context.go new file mode 100644 index 0000000..8761f79 --- /dev/null +++ b/kadai4/zaneli/omikuji/omikuji/context.go @@ -0,0 +1,21 @@ +package omikuji + +import ( + "context" + "net/http" + "time" +) + +type contextKey string + +const datetimeContextKey contextKey = "datetime" + +// AddCurrentDateTime は Context に現在日時を設定する。 +// refer: https://gocodecloud.com/blog/2016/11/15/simple-golang-http-request-context-example/ +func AddCurrentDateTime(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + date := time.Now().In(time.FixedZone("Asia/Tokyo", 9*60*60)) + ctx := context.WithValue(r.Context(), datetimeContextKey, date) + next.ServeHTTP(w, r.WithContext(ctx)) + }) +} diff --git a/kadai4/zaneli/omikuji/omikuji/debug.test b/kadai4/zaneli/omikuji/omikuji/debug.test new file mode 100755 index 0000000..2b5d294 Binary files /dev/null and b/kadai4/zaneli/omikuji/omikuji/debug.test differ diff --git a/kadai4/zaneli/omikuji/omikuji/handler.go b/kadai4/zaneli/omikuji/omikuji/handler.go new file mode 100644 index 0000000..0657ce9 --- /dev/null +++ b/kadai4/zaneli/omikuji/omikuji/handler.go @@ -0,0 +1,51 @@ +package omikuji + +import ( + "encoding/json" + "math/rand" + "net/http" + "time" +) + +var 大吉 = "大吉" +var 中吉 = "中吉" +var 吉 = "吉" +var 小吉 = "小吉" +var 凶 = "凶" +var 結果 = []string{大吉, 中吉, 吉, 小吉, 凶} + +// Result はレスポンスJSONの構造を表す。 +type Result struct { + Result string `json:"result"` + Date string `json:"date"` +} + +// Handler はおみくじの結果を返す。 +func Handler(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + v := ctx.Value(datetimeContextKey) + if v == nil { + v = time.Now() + } + date, ok := v.(time.Time) + if !ok { + date = time.Now() + } + + var result string + if is三が日(date) { + result = 大吉 + } else { + rand.Shuffle(len(結果), func(i, j int) { + 結果[i], 結果[j] = 結果[j], 結果[i] + }) + result = 結果[0] + } + w.Header().Set("Content-Type", "application/json; charset=UTF-8") + json.NewEncoder(w).Encode(Result{Result: result, Date: date.Format(time.RFC3339)}) +} + +func is三が日(date time.Time) bool { + _, m, d := date.Date() + return m == time.January && (d == 1 || d == 2 || d == 3) +} diff --git a/kadai4/zaneli/omikuji/omikuji/handler_test.go b/kadai4/zaneli/omikuji/omikuji/handler_test.go new file mode 100644 index 0000000..e314bb7 --- /dev/null +++ b/kadai4/zaneli/omikuji/omikuji/handler_test.go @@ -0,0 +1,110 @@ +package omikuji + +import ( + "context" + "encoding/json" + "io/ioutil" + "math/rand" + "net/http" + "net/http/httptest" + "testing" + "time" +) + +var jst = time.FixedZone("Asia/Tokyo", 9*60*60) + +func contains(expecteds []string, actual string) bool { + for _, expected := range expecteds { + if expected == actual { + return true + } + } + return false +} + +func Testランダムに結果を返す(t *testing.T) { + s := httptest.NewServer(http.HandlerFunc(Handler)) + defer s.Close() + + for i := 0; i < 100; i++ { + res, err := http.Get(s.URL) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + defer res.Body.Close() + + if res.StatusCode != http.StatusOK { + t.Errorf("unexpected status code: %d", res.StatusCode) + } + + contentType := res.Header.Get("Content-Type") + if contentType != "application/json; charset=UTF-8" { + t.Errorf("unexpected content type: %s", contentType) + } + + body, err := ioutil.ReadAll(res.Body) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + + var result Result + if err := json.Unmarshal(body, &result); err != nil { + t.Errorf("unexpected error: %v", err) + } + + if !(contains(結果, result.Result)) { + t.Errorf("unexpected contents: %s", result.Result) + } + + if _, err := time.Parse(time.RFC3339, result.Date); err != nil { + t.Errorf("unexpected error: %v", err) + } + } +} + +// refer: https://blog.questionable.services/article/testing-http-handlers-go/ +func Test三が日は大吉を返す(t *testing.T) { + recorder := httptest.NewRecorder() + handler := http.HandlerFunc(Handler) + rand.Seed(time.Now().UnixNano()) + + days := []int{1, 2, 3} + for _, day := range days { + for i := 0; i < 100; i++ { + date := time.Date(2018, time.January, day, rand.Intn(24), rand.Intn(60), rand.Intn(60), rand.Intn(1e9), jst) + + req, err := http.NewRequest("GET", "/", nil) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + ctx := context.WithValue(req.Context(), datetimeContextKey, date) + handler.ServeHTTP(recorder, req.WithContext(ctx)) + + if recorder.Code != http.StatusOK { + t.Errorf("unexpected status code: %d", recorder.Code) + } + contentType := recorder.Header().Get("Content-Type") + if contentType != "application/json; charset=UTF-8" { + t.Errorf("unexpected content type: %s", contentType) + } + + body, err := ioutil.ReadAll(recorder.Body) + if err != nil { + t.Errorf("unexpected error: %v", err) + } + + var result Result + if err := json.Unmarshal(body, &result); err != nil { + t.Errorf("unexpected error: %v", err) + } + + if !(contains(結果, result.Result)) { + t.Errorf("unexpected contents: %s", result.Result) + } + + if result.Date != date.Format(time.RFC3339) { + t.Errorf("unexpected date: %v", result.Date) + } + } + } +}