diff --git a/kadai4/tsuchinaga/README.md b/kadai4/tsuchinaga/README.md new file mode 100644 index 0000000..5f30802 --- /dev/null +++ b/kadai4/tsuchinaga/README.md @@ -0,0 +1,17 @@ +# 課題4 tsuchinaga + +## おみくじAPIを作ってみよう + +* JSON形式でおみくじの結果を返す +* 正月(1/1-1/3)だけ大吉にする +* ハンドラのテストを書いてみる + +## TODO +* [x] おみじくの結果をJSONで返すWebAPIをつくる + * [x] サーバを立てる + * [x] サーバのポートを標準出力に出す + * [x] `/fortune` でおみくじの結果をJSONで返す + * [x] おみくじの結果をつくる + * [x] 1/1~1/3の期間は全て大吉 + * [x] 1/1~1/3以外の期間はランダム + * [x] テストのために、日時を外から注入できるようにする diff --git a/kadai4/tsuchinaga/clock/clock.go b/kadai4/tsuchinaga/clock/clock.go new file mode 100644 index 0000000..161a22a --- /dev/null +++ b/kadai4/tsuchinaga/clock/clock.go @@ -0,0 +1,15 @@ +package clock + +import "time" + +func New() Clock { return &clock{} } + +type Clock interface { + Now() time.Time +} + +type clock struct{} + +func (c *clock) Now() time.Time { + return time.Now() +} diff --git a/kadai4/tsuchinaga/clock/clock_test.go b/kadai4/tsuchinaga/clock/clock_test.go new file mode 100644 index 0000000..5940558 --- /dev/null +++ b/kadai4/tsuchinaga/clock/clock_test.go @@ -0,0 +1,24 @@ +package clock + +import ( + "reflect" + "testing" + "time" +) + +func Test_clock_Now(t *testing.T) { + c := &clock{} + got := c.Now() + now := time.Now() + if got.After(now) { + t.Errorf("Now() = %v, want before %v", got, now) + } +} + +func TestNew(t *testing.T) { + want := &clock{} + got := New() + if !reflect.DeepEqual(got, want) { + t.Errorf("New() = %v, want %v", got, want) + } +} diff --git a/kadai4/tsuchinaga/fortune/fortune.go b/kadai4/tsuchinaga/fortune/fortune.go new file mode 100644 index 0000000..b64b34a --- /dev/null +++ b/kadai4/tsuchinaga/fortune/fortune.go @@ -0,0 +1,55 @@ +package fortune + +import ( + "math/rand" + "time" +) + +// Foredoom - 吉凶 +type Foredoom string + +const ( + ForedoomDaikichi Foredoom = "大吉" + ForedoomKichi Foredoom = "吉" + ForedoomChukichi Foredoom = "中吉" + ForedoomShokichi Foredoom = "小吉" + ForedoomKyou Foredoom = "凶" +) + +var foredooms = []Foredoom{ForedoomDaikichi, ForedoomKichi, ForedoomChukichi, ForedoomShokichi, ForedoomKyou} + +// Paper - 結果の載ってる紙 +type Paper struct { + No int `json:"no"` // おみくじ番号 [1,100] + Foredoom Foredoom `json:"foredoom"` // 吉凶 + Wish Foredoom `json:"wish"` // 願望 + Health Foredoom `json:"health"` // 健康 + Business Foredoom `json:"business"` // 仕事 + Love Foredoom `json:"love"` // 恋愛 + Study Foredoom `json:"study"` // 勉強 +} + +func Get(now time.Time) Paper { + if now.Month() == 1 && now.Day() <= 3 { + return Paper{ + No: now.Day(), + Foredoom: ForedoomDaikichi, + Wish: ForedoomDaikichi, + Health: ForedoomDaikichi, + Business: ForedoomDaikichi, + Love: ForedoomDaikichi, + Study: ForedoomDaikichi, + } + } + + r := rand.New(rand.NewSource(now.UnixNano())) + return Paper{ + No: r.Intn(100) + 1, + Foredoom: foredooms[r.Intn(5)], + Wish: foredooms[r.Intn(5)], + Health: foredooms[r.Intn(5)], + Business: foredooms[r.Intn(5)], + Love: foredooms[r.Intn(5)], + Study: foredooms[r.Intn(5)], + } +} diff --git a/kadai4/tsuchinaga/fortune/fortune_test.go b/kadai4/tsuchinaga/fortune/fortune_test.go new file mode 100644 index 0000000..134e35f --- /dev/null +++ b/kadai4/tsuchinaga/fortune/fortune_test.go @@ -0,0 +1,111 @@ +package fortune + +import ( + "reflect" + "testing" + "time" +) + +func TestGet(t *testing.T) { + tests := []struct { + name string + arg time.Time + want Paper + }{ + {name: "2020/01/01 00:00:00は1番で全部大吉", + arg: time.Date(2020, 1, 1, 0, 0, 0, 0, time.Local), + want: Paper{ + No: 1, + Foredoom: ForedoomDaikichi, + Wish: ForedoomDaikichi, + Health: ForedoomDaikichi, + Business: ForedoomDaikichi, + Love: ForedoomDaikichi, + Study: ForedoomDaikichi, + }}, + {name: "2020/01/01 23:59:59は1番で全部大吉", + arg: time.Date(2020, 1, 1, 23, 59, 59, 0, time.Local), + want: Paper{ + No: 1, + Foredoom: ForedoomDaikichi, + Wish: ForedoomDaikichi, + Health: ForedoomDaikichi, + Business: ForedoomDaikichi, + Love: ForedoomDaikichi, + Study: ForedoomDaikichi, + }}, + {name: "2020/01/02は2番で全部大吉", + arg: time.Date(2020, 1, 2, 0, 0, 0, 0, time.Local), + want: Paper{ + No: 2, + Foredoom: ForedoomDaikichi, + Wish: ForedoomDaikichi, + Health: ForedoomDaikichi, + Business: ForedoomDaikichi, + Love: ForedoomDaikichi, + Study: ForedoomDaikichi, + }}, + {name: "2020/01/03は3番で全部大吉", + arg: time.Date(2020, 1, 3, 0, 0, 0, 0, time.Local), + want: Paper{ + No: 3, + Foredoom: ForedoomDaikichi, + Wish: ForedoomDaikichi, + Health: ForedoomDaikichi, + Business: ForedoomDaikichi, + Love: ForedoomDaikichi, + Study: ForedoomDaikichi, + }}, + {name: "2030/01/01は1番で全部大吉", + arg: time.Date(2030, 1, 1, 0, 0, 0, 0, time.Local), + want: Paper{ + No: 1, + Foredoom: ForedoomDaikichi, + Wish: ForedoomDaikichi, + Health: ForedoomDaikichi, + Business: ForedoomDaikichi, + Love: ForedoomDaikichi, + Study: ForedoomDaikichi, + }}, + {name: "2040/01/01は1番で全部大吉", + arg: time.Date(2040, 1, 1, 0, 0, 0, 0, time.Local), + want: Paper{ + No: 1, + Foredoom: ForedoomDaikichi, + Wish: ForedoomDaikichi, + Health: ForedoomDaikichi, + Business: ForedoomDaikichi, + Love: ForedoomDaikichi, + Study: ForedoomDaikichi, + }}, + {name: "2020/01/04 00:00:00はランダム", + arg: time.Date(2020, 1, 4, 0, 0, 0, 0, time.Local), + want: Paper{ + No: 30, + Foredoom: ForedoomChukichi, + Wish: ForedoomKyou, + Health: ForedoomDaikichi, + Business: ForedoomDaikichi, + Love: ForedoomDaikichi, + Study: ForedoomKyou, + }}, + {name: "2020/01/04 00:00:01はランダム", + arg: time.Date(2020, 1, 4, 0, 0, 1, 0, time.Local), + want: Paper{ + No: 53, + Foredoom: ForedoomKichi, + Wish: ForedoomShokichi, + Health: ForedoomShokichi, + Business: ForedoomKyou, + Love: ForedoomKyou, + Study: ForedoomKichi, + }}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := Get(tt.arg); !reflect.DeepEqual(got, tt.want) { + t.Errorf("Get() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/kadai4/tsuchinaga/go.mod b/kadai4/tsuchinaga/go.mod new file mode 100644 index 0000000..8bf1b1c --- /dev/null +++ b/kadai4/tsuchinaga/go.mod @@ -0,0 +1,3 @@ +module github.com/gopherdojo/dojo8/kadai4/tsuchinaga + +go 1.14 diff --git a/kadai4/tsuchinaga/main.go b/kadai4/tsuchinaga/main.go new file mode 100644 index 0000000..8f2d02f --- /dev/null +++ b/kadai4/tsuchinaga/main.go @@ -0,0 +1,31 @@ +package main + +import ( + "fmt" + "log" + "os" + "time" + + "github.com/gopherdojo/dojo8/kadai4/tsuchinaga/clock" + + "github.com/gopherdojo/dojo8/kadai4/tsuchinaga/server" +) + +func main() { + serv := server.New(clock.New()) + go func() { + if err := serv.Run(); err != nil { + log.Println(err) + os.Exit(2) + } + }() + + for { + time.Sleep(100 * time.Millisecond) + if serv.GetAddr() != "" { + fmt.Printf("server started at %s\n", serv.GetAddr()) + break + } + } + <-make(chan error) +} diff --git a/kadai4/tsuchinaga/server/server.go b/kadai4/tsuchinaga/server/server.go new file mode 100644 index 0000000..5005ba2 --- /dev/null +++ b/kadai4/tsuchinaga/server/server.go @@ -0,0 +1,64 @@ +package server + +import ( + "context" + "encoding/json" + "net" + "net/http" + + "github.com/gopherdojo/dojo8/kadai4/tsuchinaga/clock" + + "github.com/gopherdojo/dojo8/kadai4/tsuchinaga/fortune" +) + +func New(clock clock.Clock) Server { + return &server{clock: clock} +} + +type Server interface { + GetAddr() string + Run() error + Stop(ctx context.Context) error +} + +type server struct { + addr string + serv *http.Server + clock clock.Clock +} + +func (s *server) GetAddr() string { + return s.addr +} + +func (s *server) Run() error { + ln, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + return err + } + defer ln.Close() + s.addr = ln.Addr().String() + + mux := http.NewServeMux() + mux.HandleFunc("/fortune", s.FortuneHandler) + s.serv = &http.Server{Handler: mux} + return s.serv.Serve(ln) +} + +func (s *server) Stop(ctx context.Context) error { + if s.serv != nil { + return s.serv.Shutdown(ctx) + } + return nil +} + +func (s *server) FortuneHandler(w http.ResponseWriter, _ *http.Request) { + res, err := json.Marshal(fortune.Get(s.clock.Now())) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + w.Header().Set("Content-Type", "application/json") + _, _ = w.Write(res) +} diff --git a/kadai4/tsuchinaga/server/server_test.go b/kadai4/tsuchinaga/server/server_test.go new file mode 100644 index 0000000..06a9338 --- /dev/null +++ b/kadai4/tsuchinaga/server/server_test.go @@ -0,0 +1,105 @@ +package server + +import ( + "fmt" + "io/ioutil" + "log" + "net/http" + "net/http/httptest" + "testing" + "time" + + "github.com/gopherdojo/dojo8/kadai4/tsuchinaga/clock" +) + +func Test_server_GetAddr(t *testing.T) { + type fields struct { + addr string + } + tests := []struct { + name string + fields fields + want string + }{ + {name: "addrが空文字なら空文字を返す", fields: fields{addr: ""}, want: ""}, + {name: "addrにアドレスが入っていたら入っているアドレスを返す", fields: fields{addr: "127.0.0.1:57024"}, want: "127.0.0.1:57024"}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + s := &server{addr: tt.fields.addr} + if got := s.GetAddr(); got != tt.want { + t.Errorf("GetAddr() = %v, want %v", got, tt.want) + } + }) + } +} + +type testClock struct{ now time.Time } + +func (t *testClock) Now() time.Time { return t.now } + +func Test_server_FortuneHandler(t *testing.T) { + type fields struct{ clock clock.Clock } + tests := []struct { + name string + fields fields + want string + }{ + {name: "2020/01/01は全て大吉", + fields: fields{clock: &testClock{time.Date(2020, 1, 1, 0, 0, 0, 0, time.Local)}}, + want: `{"no":1,"foredoom":"大吉","wish":"大吉","health":"大吉","business":"大吉","love":"大吉","study":"大吉"}`}, + {name: "2020/01/02は全て大吉", + fields: fields{clock: &testClock{time.Date(2020, 1, 2, 0, 0, 0, 0, time.Local)}}, + want: `{"no":2,"foredoom":"大吉","wish":"大吉","health":"大吉","business":"大吉","love":"大吉","study":"大吉"}`}, + {name: "2020/01/03は全て大吉", + fields: fields{clock: &testClock{time.Date(2020, 1, 2, 0, 0, 0, 0, time.Local)}}, + want: `{"no":3,"foredoom":"大吉","wish":"大吉","health":"大吉","business":"大吉","love":"大吉","study":"大吉"}`}, + {name: "2020/01/04はランダム", + fields: fields{clock: &testClock{time.Date(2020, 1, 4, 0, 0, 0, 0, time.Local)}}, + want: `{"no":30,"foredoom":"中吉","wish":"凶","health":"大吉","business":"大吉","love":"大吉","study":"凶"}`}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + s := &server{clock: tt.fields.clock} + mux := http.NewServeMux() + mux.HandleFunc("/", s.FortuneHandler) + ts := httptest.NewServer(mux) + defer ts.Close() + req, _ := http.NewRequest("GET", ts.URL, nil) + resp, err := new(http.Client).Do(req) + if err != nil { + t.Errorf("%s 失敗\nリクエストエラー: %v\n", t.Name(), err) + return + } + defer resp.Body.Close() + b, err := ioutil.ReadAll(resp.Body) + if err != nil { + t.Errorf("%s 失敗\n読み込みエラー: %v\n", t.Name(), err) + return + } + log.Printf("%+v\n", string(b)) + }) + } +} + +func Test_server_Run(t *testing.T) { + want := `{"no":1,"foredoom":"大吉","wish":"大吉","health":"大吉","business":"大吉","love":"大吉","study":"大吉"}` + serv := &server{clock: &testClock{now: time.Date(2020, 1, 1, 0, 0, 0, 0, time.Local)}} + go func() { _ = serv.Run() }() + for { + time.Sleep(100 * time.Millisecond) + if serv.GetAddr() != "" { + break + } + } + resp, err := http.Get(fmt.Sprintf("http://%s/fortune", serv.GetAddr())) + if err != nil { + t.Errorf("%s エラー\n%v\n", t.Name(), err) + return + } + defer resp.Body.Close() + b, _ := ioutil.ReadAll(resp.Body) + if got := string(b); got != want { + t.Errorf("%s 失敗\n期待: %s\n実際: %s\n", t.Name(), want, got) + } +}