diff --git a/kadai4/matsuki/lib/entities.go b/kadai4/matsuki/lib/entities.go new file mode 100644 index 0000000..b8ff12c --- /dev/null +++ b/kadai4/matsuki/lib/entities.go @@ -0,0 +1,38 @@ +package lib + +import ( + "time" +) + +// FortuneResult は、おみくじを引いた結果 +// - JSON返却値 +type FortuneResult struct { + Today jsonTime `json:"today"` + Fortune string `json:"fortune"` + IsNewyearMode bool `json:"is_newyear_mode"` +} + +// 独自のjsonTimeを作成 +// - https://qiita.com/taizo/items/2c3a338f1aeea86ce9e2 +type jsonTime struct { + time.Time +} + +func (j jsonTime) format() string { + return j.Time.Format("2006-01-02") +} +func (j jsonTime) parse(value string) (time.Time, error) { + return time.Parse(`"2006-01-02"`, value) +} + +// MarshalJSON() の実装 +func (j jsonTime) MarshalJSON() ([]byte, error) { + return []byte(`"` + j.format() + `"`), nil +} + +// UnmarshalJSON() の実装 +func (j jsonTime) UnmarshalJSON(data []byte) error { + tm, err := j.parse(string(data)) + j.Time = tm + return err +} diff --git a/kadai4/matsuki/lib/fortune.go b/kadai4/matsuki/lib/fortune.go new file mode 100644 index 0000000..46429d4 --- /dev/null +++ b/kadai4/matsuki/lib/fortune.go @@ -0,0 +1,62 @@ +package lib + +import ( + "log" + "math/rand" + "time" +) + +// Verbos は、ログ出力レベルを制御 +var Verbos bool + +// Fortune は、おみくじに関する型 +type Fortune struct { + fortuneList map[uint]string + luckiest string + today time.Time +} + +// NewFortune は、おみくじの初期化 +func NewFortune(now time.Time) Fortune { + // おみくじのタネを初期化 + rand.Seed(time.Now().UnixNano()) + + return Fortune{ + fortuneList: map[uint]string{ + 0: "大吉", + 1: "中吉", + 2: "吉", + 3: "凶", + 4: "大凶", + }, + luckiest: "大吉", + today: now, + } +} + +func (f Fortune) isNewyearsDay() bool { + if Verbos { + log.Printf("[DEBUG] today is %v %v", f.today.Day(), f.today.Month()) + } + + // Month is defined here: https://golang.org/pkg/time/#Month + if f.today.Month().String() == "January" { + switch f.today.Day() { + case 1, 2, 3: + return true + } + } + return false +} + +// DrawFortuneSlip は、おみくじを引く関数 +// return ( 運勢, is正月 ) +func (f Fortune) DrawFortuneSlip() (string, bool) { + // 正月には大吉を引く + if f.isNewyearsDay() { + return f.luckiest, true + } + // 等確率でひく + r := rand.Intn(len(f.fortuneList)) + return f.fortuneList[uint(r)], false +} diff --git a/kadai4/matsuki/lib/fortune_test.go b/kadai4/matsuki/lib/fortune_test.go new file mode 100644 index 0000000..77c6ced --- /dev/null +++ b/kadai4/matsuki/lib/fortune_test.go @@ -0,0 +1,56 @@ +package lib + +import ( + "log" + "math/rand" + "testing" + "time" + + "github.com/gopherdojo/dojo1/kadai4/matsuki/lib" +) + +// TestMain called first +func TestMain(t *testing.T) { + + beforeTest() + + // test here + // TestIndex(t) + +} + +func beforeTest() { + rand.Seed(time.Now().UnixNano()) + lib.Verbos = true +} + +func TestDrawFortuneSlip(t *testing.T) { + f := lib.NewFortune(time.Now()) + slip, _ := f.DrawFortuneSlip() + switch slip { + case "大吉", "中吉", "吉", "凶", "大凶": + log.Print("draw valid slip") + default: + t.Errorf("invalid slip=%v", slip) + } +} + +func TestDrawWhenNewyear(t *testing.T) { + tm, err := time.Parse("2006-01-02", "2013-01-03") + log.Println("test date=", tm, err) + + f := lib.NewFortune(tm) + slip, isNd := f.DrawFortuneSlip() + switch slip { + case "大吉": + log.Print("draw valid slip") + case "中吉", "吉", "凶", "大凶": + t.Errorf("today is newYear. invalid slip=%v", slip) + default: + t.Errorf("invalid slip=%v", slip) + } + + if isNd != true { + t.Errorf("want isNewYearDay is true but %v", isNd) + } +} diff --git a/kadai4/matsuki/lib/server.go b/kadai4/matsuki/lib/server.go new file mode 100644 index 0000000..a481945 --- /dev/null +++ b/kadai4/matsuki/lib/server.go @@ -0,0 +1,81 @@ +package lib + +import ( + "encoding/json" + "log" + "net/http" + "time" +) + +var drawer Drawer + +// Drawer は、おみくじを引くinterface +type Drawer interface { + DrawFortuneSlip() (string, bool) +} + +// NewServer is constoructor +// Drawer interfaceを使うことでunitTestしやすくする +func NewServer(d Drawer) http.Server { + SetDrawer(d) // パッケージ内部グローバル変数にセット + + m := http.NewServeMux() + // Routing + m.Handle("/twice", http.HandlerFunc(Twice)) + m.Handle("/", http.HandlerFunc(Index)) + + server := http.Server{ + Addr: ":8080", + Handler: m, + } + log.Printf("[INFO] server start") + return server +} + +// SetDrawer is setter +func SetDrawer(d Drawer) { + drawer = d +} + +// Index では、おみくじを引き、結果をJSONで返却 +func Index(w http.ResponseWriter, req *http.Request) { + if req.URL.Path != "/" { + log.Printf("[INFO] index() with path = %v", req.URL.Path) + http.NotFound(w, req) + return + } + + fortuneRes, isNewyearDay := drawer.DrawFortuneSlip() + log.Printf("[INFO] index() with fourtune=%v, isNewyearDay=%v", fortuneRes, isNewyearDay) + // w.Write([]byte("Hello world = " + fortuneRes)) + res := FortuneResult{ + Today: jsonTime{time.Now()}, + Fortune: fortuneRes, + IsNewyearMode: isNewyearDay, + } + if err := json.NewEncoder(w).Encode(res); err != nil { + panic(err) + } +} + +// Twice では、おみくじを2回連続で引き、JSONで返却 +func Twice(w http.ResponseWriter, req *http.Request) { + fFirst, isNdFist := drawer.DrawFortuneSlip() + fSecond, isNdSecond := drawer.DrawFortuneSlip() + + res := []FortuneResult{ + FortuneResult{ + Today: jsonTime{time.Now()}, + Fortune: fFirst, + IsNewyearMode: isNdFist, + }, + FortuneResult{ + Today: jsonTime{time.Now()}, + Fortune: fSecond, + IsNewyearMode: isNdSecond, + }, + } + if err := json.NewEncoder(w).Encode(res); err != nil { + panic(err) + } +} diff --git a/kadai4/matsuki/lib/server_test.go b/kadai4/matsuki/lib/server_test.go new file mode 100644 index 0000000..8107e04 --- /dev/null +++ b/kadai4/matsuki/lib/server_test.go @@ -0,0 +1,94 @@ +package lib + +import ( + "encoding/json" + "io/ioutil" + "log" + "net/http" + "net/http/httptest" + "testing" + + "github.com/gopherdojo/dojo1/kadai4/matsuki/lib" +) + +type MockDrawer struct { +} + +// DrawFortuneSlip is Mock +func (m MockDrawer) DrawFortuneSlip() (string, bool) { + return "大吉", false +} + +// TestMain called first +func TestMain(t *testing.T) { + + beforeTest() + + // test here + // TestIndex(t) + +} + +func beforeTest() { + lib.SetDrawer(MockDrawer{}) +} + +func TestIndex(t *testing.T) { + ts := httptest.NewServer(http.HandlerFunc(lib.Index)) + defer ts.Close() + + res, err := http.Get(ts.URL) + if err != nil { + t.Error("unexpected") + return + } + + if res.StatusCode != 200 { + t.Error("Status code error") + return + } + + //レスポンスBODY取得 + body, _ := ioutil.ReadAll(res.Body) + have := string(body) + log.Print(have) + + fortuneRes := lib.FortuneResult{} + if err := json.Unmarshal([]byte(have), &fortuneRes); err != nil { + t.Errorf("response body cannot unmarshal. err=%v, body=%v", err, have) + } + + // now := time.Now() + // today := now.Format("2006-01-02") + // want := fmt.Sprint(`{"today":"`, today, `","fortune":"大吉","is_newyear_mode":false}`) + // + // if have != want { + // t.Errorf("response body error, want %v but have %v", want, have) + // } +} + +func TestTwice(t *testing.T) { + ts := httptest.NewServer(http.HandlerFunc(lib.Twice)) + defer ts.Close() + + res, err := http.Get(ts.URL) + if err != nil { + t.Error("unexpected") + return + } + + if res.StatusCode != 200 { + t.Error("Status code error") + return + } + + //レスポンスBODY取得 & JSON復元できるか + body, _ := ioutil.ReadAll(res.Body) + have := string(body) + log.Print(have) + + fortuneRess := []lib.FortuneResult{} + if err := json.Unmarshal([]byte(have), &fortuneRess); err != nil { + t.Errorf("response body cannot unmarshal. err=%v, body=%v", err, have) + } +} diff --git a/kadai4/matsuki/main.go b/kadai4/matsuki/main.go new file mode 100644 index 0000000..0c89e85 --- /dev/null +++ b/kadai4/matsuki/main.go @@ -0,0 +1,18 @@ +package main + +import ( + "time" + + "github.com/gopherdojo/dojo1/kadai4/matsuki/lib" +) + +var verbos bool + +func main() { + lib.Verbos = true + + f := lib.NewFortune(time.Now()) + // server + s := lib.NewServer(f) + s.ListenAndServe() +}