diff --git a/kadai4/segakazzz/.gitignore b/kadai4/segakazzz/.gitignore new file mode 100644 index 0000000..a4ebc28 --- /dev/null +++ b/kadai4/segakazzz/.gitignore @@ -0,0 +1,2 @@ +.idea +cmd/omkj \ No newline at end of file diff --git a/kadai4/segakazzz/README.md b/kadai4/segakazzz/README.md new file mode 100644 index 0000000..efdb5f4 --- /dev/null +++ b/kadai4/segakazzz/README.md @@ -0,0 +1,39 @@ +#【TRY】おみくじAPI +## おみくじAPIを作ってみよう +- JSON形式でおみくじの結果を返す +- 正月(1/1-1/3)だけ大吉にする +- ハンドラのテストを書いてみる + +## 回答 +### 実行方法 +~~~ +$ cd cmd +$ go build -o omkj main.go +$ ./omkj +Server is running with port 8080👍 +~~~ +上記実行後任意のブラウザでアクセスすると以下のような結果が帰ってくる +~~~ +{ + "time": "2020-07-26T22:54:31.541464+09:00", + "dice": 1, + "result": "凶" +} +~~~ + +### 処理説明補足 +- 以前作成した[Try]おみくじプログラムを作ろうを応用しました。 +- テストパッケージをomikuji_testとして作成しました。(課題2は同じパッケージ内で行った) +- テストでは勉強のため、一部のテストをStandard Library提供の関数をMockして実行しました。 + +~~~ +$ cd omikuji +$ go test --cover --coverprofile=coverprofile.out +Server is starting with port 8000 👍 +PASS +coverage: 97.6% of statements +ok github.com/gopherdojo/dojo8/kadai4/segakazzz/omikuji 0.016s +~~~ + +## 感想 +- APIの作成自体はすんなり終わりましたが、テストコードの作成にかなり苦戦しました \ No newline at end of file diff --git a/kadai4/segakazzz/cmd/go.mod b/kadai4/segakazzz/cmd/go.mod new file mode 100644 index 0000000..7c1acd3 --- /dev/null +++ b/kadai4/segakazzz/cmd/go.mod @@ -0,0 +1,10 @@ +module github.com/gopherdojo/dojo8/kadai4/segakazzz/cmd + +go 1.14 + +replace github.com/gopherdojo/dojo8/kadai4/segakazzz/omikuji => ../omikuji + +require ( + github.com/gopherdojo/dojo8/kadai4/segakazzz/omikuji v0.0.0-00010101000000-000000000000 // indirect + github.com/pkg/errors v0.9.1 // indirect +) diff --git a/kadai4/segakazzz/cmd/go.sum b/kadai4/segakazzz/cmd/go.sum new file mode 100644 index 0000000..7c401c3 --- /dev/null +++ b/kadai4/segakazzz/cmd/go.sum @@ -0,0 +1,2 @@ +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= diff --git a/kadai4/segakazzz/cmd/main.go b/kadai4/segakazzz/cmd/main.go new file mode 100644 index 0000000..e346706 --- /dev/null +++ b/kadai4/segakazzz/cmd/main.go @@ -0,0 +1,24 @@ +package main + +import ( + "flag" + "fmt" + "github.com/gopherdojo/dojo8/kadai4/segakazzz/omikuji" + "os" +) + +var port int + +func init() { + flag.IntVar(&port, "p", 8080, "Port Number to serve http") +} + +func main() { + flag.Parse() + err := omikuji.Run(port) + if err != nil { + fmt.Println(err) + os.Exit(1) + } + os.Exit(0) +} diff --git a/kadai4/segakazzz/omikuji/coverprofile.out b/kadai4/segakazzz/omikuji/coverprofile.out new file mode 100644 index 0000000..6252903 --- /dev/null +++ b/kadai4/segakazzz/omikuji/coverprofile.out @@ -0,0 +1,27 @@ +mode: set +github.com/gopherdojo/dojo8/kadai4/segakazzz/omikuji/omikuji.go:33.28,35.2 1 1 +github.com/gopherdojo/dojo8/kadai4/segakazzz/omikuji/omikuji.go:37.74,39.16 2 1 +github.com/gopherdojo/dojo8/kadai4/segakazzz/omikuji/omikuji.go:43.2,44.16 2 1 +github.com/gopherdojo/dojo8/kadai4/segakazzz/omikuji/omikuji.go:47.2,48.13 2 1 +github.com/gopherdojo/dojo8/kadai4/segakazzz/omikuji/omikuji.go:39.16,42.3 2 1 +github.com/gopherdojo/dojo8/kadai4/segakazzz/omikuji/omikuji.go:44.16,46.3 1 1 +github.com/gopherdojo/dojo8/kadai4/segakazzz/omikuji/omikuji.go:51.26,56.16 5 1 +github.com/gopherdojo/dojo8/kadai4/segakazzz/omikuji/omikuji.go:59.2,59.12 1 0 +github.com/gopherdojo/dojo8/kadai4/segakazzz/omikuji/omikuji.go:56.16,58.3 1 1 +github.com/gopherdojo/dojo8/kadai4/segakazzz/omikuji/omikuji.go:62.38,66.2 3 1 +github.com/gopherdojo/dojo8/kadai4/segakazzz/omikuji/omikuji.go:68.50,69.11 1 1 +github.com/gopherdojo/dojo8/kadai4/segakazzz/omikuji/omikuji.go:70.9,71.20 1 1 +github.com/gopherdojo/dojo8/kadai4/segakazzz/omikuji/omikuji.go:72.12,73.20 1 1 +github.com/gopherdojo/dojo8/kadai4/segakazzz/omikuji/omikuji.go:74.12,75.23 1 1 +github.com/gopherdojo/dojo8/kadai4/segakazzz/omikuji/omikuji.go:76.9,77.23 1 1 +github.com/gopherdojo/dojo8/kadai4/segakazzz/omikuji/omikuji.go:78.10,79.48 1 1 +github.com/gopherdojo/dojo8/kadai4/segakazzz/omikuji/omikuji.go:83.38,86.26 3 1 +github.com/gopherdojo/dojo8/kadai4/segakazzz/omikuji/omikuji.go:91.2,92.12 2 1 +github.com/gopherdojo/dojo8/kadai4/segakazzz/omikuji/omikuji.go:86.26,88.3 1 1 +github.com/gopherdojo/dojo8/kadai4/segakazzz/omikuji/omikuji.go:88.8,90.3 1 1 +github.com/gopherdojo/dojo8/kadai4/segakazzz/omikuji/omikuji.go:95.45,97.16 2 1 +github.com/gopherdojo/dojo8/kadai4/segakazzz/omikuji/omikuji.go:100.2,100.16 1 1 +github.com/gopherdojo/dojo8/kadai4/segakazzz/omikuji/omikuji.go:97.16,99.3 1 1 +github.com/gopherdojo/dojo8/kadai4/segakazzz/omikuji/omikuji.go:103.44,105.26 2 1 +github.com/gopherdojo/dojo8/kadai4/segakazzz/omikuji/omikuji.go:108.2,108.14 1 1 +github.com/gopherdojo/dojo8/kadai4/segakazzz/omikuji/omikuji.go:105.26,107.3 1 1 diff --git a/kadai4/segakazzz/omikuji/export_test.go b/kadai4/segakazzz/omikuji/export_test.go new file mode 100644 index 0000000..ed78c81 --- /dev/null +++ b/kadai4/segakazzz/omikuji/export_test.go @@ -0,0 +1,44 @@ +package omikuji + +import ( + "fmt" + "time" +) + +var NewOmikuji = newOmikuji +var OmikujiHandler = (*Omikuji).omikujiHandler +var ThrowOneToSix = (*Omikuji).throwOneToSix +var IntToStr = (*Omikuji).intToStr +var IsNewYearHoliday = (*Omikuji).isNewYearHoliday +var GenJson = (*Omikuji).genJson +var TryOmikuji = (*Omikuji).tryOmikuji + +var StdStdMethods = StdMethods + +var ErrStdMethods = StdLibProvider{ + JsonMarshal: func(v interface{}) ([]byte, error){ + return nil, fmt.Errorf("json.marshal dummy error...") + }, +} + +var MockStdMethods = StdLibProvider{ + RandIntn:func(i int) int { + return 5 + }, +} + +var MockHoliday = StdLibProvider{ + TimeNow: func () time.Time { + return time.Date(2020, time.January, 1, 10,23,30,9, time.UTC) + }, +} + +var MockNotHoliday = StdLibProvider{ + TimeNow: func () time.Time { + return time.Date(2020, time.March, 1, 10,23,30,9, time.UTC) + }, + RandIntn:func(i int) int { + return 5 + }, +} + diff --git a/kadai4/segakazzz/omikuji/go.mod b/kadai4/segakazzz/omikuji/go.mod new file mode 100644 index 0000000..27404fc --- /dev/null +++ b/kadai4/segakazzz/omikuji/go.mod @@ -0,0 +1,5 @@ +module github.com/gopherdojo/dojo8/kadai4/segakazzz/omikuji + +go 1.14 + +require github.com/pkg/errors v0.9.1 diff --git a/kadai4/segakazzz/omikuji/go.sum b/kadai4/segakazzz/omikuji/go.sum new file mode 100644 index 0000000..7c401c3 --- /dev/null +++ b/kadai4/segakazzz/omikuji/go.sum @@ -0,0 +1,2 @@ +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= diff --git a/kadai4/segakazzz/omikuji/omikuji.go b/kadai4/segakazzz/omikuji/omikuji.go new file mode 100644 index 0000000..c060454 --- /dev/null +++ b/kadai4/segakazzz/omikuji/omikuji.go @@ -0,0 +1,109 @@ +package omikuji + +import ( + "encoding/json" + "fmt" + "github.com/pkg/errors" + "math/rand" + "net/http" + "strconv" + "time" +) + +type Omikuji struct { + DateTime time.Time `json:"time"` + Dice int `json:"dice"` + Result string `json:"result"` +} + +type StdLibProvider struct { + JsonMarshal func(v interface{}) ([]byte, error) + RandIntn func(int) int + TimeNow func() time.Time + HttpListenAndServe func (addr string, handler http.Handler) error +} + +var StdMethods = StdLibProvider{ + JsonMarshal: json.Marshal, + RandIntn: rand.Intn, + TimeNow: time.Now, + HttpListenAndServe: http.ListenAndServe, +} + +func newOmikuji() *Omikuji { + return &Omikuji{} +} + +func (o *Omikuji) omikujiHandler(w http.ResponseWriter, r *http.Request) { + err := o.tryOmikuji() + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + js, err := o.genJson() + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + } + w.Header().Set("Content-Type", "application/json") + w.Write(js) +} + +func Run(port int) error { + o := newOmikuji() + http.HandleFunc("/", o.omikujiHandler) + fmt.Println("Server is starting with port " +strconv.Itoa(port), "👍") + err := StdMethods.HttpListenAndServe(":" + strconv.Itoa(port), nil) + if err != nil { + return errors.Wrapf(err, "Error in Run()\n") + } + return nil +} + +func (o *Omikuji)throwOneToSix() int { + rand.Seed(o.DateTime.UnixNano()) + i := StdMethods.RandIntn(6) + return i + 1 +} + +func (o *Omikuji)intToStr(n int) (string, error) { + switch n { + case 1: + return "凶", nil + case 2, 3: + return "吉", nil + case 4, 5: + return "中吉", nil + case 6: + return "大吉", nil + default: + return "", fmt.Errorf("invalid number %d", n) + } +} + +func (o *Omikuji) tryOmikuji() error { + var err error + o.DateTime = StdMethods.TimeNow() + if !o.isNewYearHoliday(){ + o.Dice = o.throwOneToSix() + } else { + o.Dice = 6 + } + o.Result, err = o.intToStr(o.Dice) + return err +} + +func (o *Omikuji) genJson() ([]byte, error) { + js, err := StdMethods.JsonMarshal(o) + if err != nil { + return nil, err + } + return js, nil +} + +func (o *Omikuji) isNewYearHoliday () bool { + _, m, d := o.DateTime.Date() + if int(m) == 1 && d <= 3{ + return true + } + return false +} diff --git a/kadai4/segakazzz/omikuji/omikuji_test.go b/kadai4/segakazzz/omikuji/omikuji_test.go new file mode 100644 index 0000000..0d7f9b1 --- /dev/null +++ b/kadai4/segakazzz/omikuji/omikuji_test.go @@ -0,0 +1,429 @@ +package omikuji_test + +import ( + "fmt" + "github.com/gopherdojo/dojo8/kadai4/segakazzz/omikuji" + "io/ioutil" + "net/http" + "net/http/httptest" + "reflect" + "testing" + "time" +) + +func TestOmikuji_genJson(t *testing.T) { + type fields struct { + DateTime time.Time + Dice int + Result string + } + tests := []struct { + name string + fields fields + want string + wantErr bool + }{ + { + name: "Expects Success 1", + fields: fields{ + DateTime: time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC), + Dice: 1, + Result: "凶", + }, + want: "{\"time\":\"2009-11-10T23:00:00Z\",\"dice\":1,\"result\":\"凶\"}", + wantErr: false, + }, + { + name: "Expects Error", + fields: fields{ + DateTime: time.Date(2020, time.July, 21, 5, 9, 23, 3424, time.UTC), + Dice: 6, + Result: "大吉", + }, + want: "", + wantErr: true, + }, + { + name: "Expects Success 2", + fields: fields{ + DateTime: time.Date(2020, time.July, 21, 5, 9, 23, 3424, time.UTC), + Dice: 6, + Result: "大吉", + }, + want: "{\"time\":\"2020-07-21T05:09:23.000003424Z\",\"dice\":6,\"result\":\"大吉\"}", + wantErr: false, + }, + } + for _, tt := range tests { + + t.Run(tt.name, func(t *testing.T) { + o := &omikuji.Omikuji{ + DateTime: tt.fields.DateTime, + Dice: tt.fields.Dice, + Result: tt.fields.Result, + } + if tt.wantErr == true { + omikuji.StdMethods = omikuji.ErrStdMethods + } else { + omikuji.StdMethods = omikuji.StdStdMethods + } + + got, err := omikuji.GenJson(o) + if (err != nil) != tt.wantErr { + t.Errorf("genJson() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(string(got), tt.want) { + t.Errorf("genJson() got = %v, want %v", string(got), string(tt.want)) + } + }) + } +} + +func TestOmikuji_intToStr(t *testing.T) { + type fields struct { + DateTime time.Time + Dice int + Result string + } + type args struct { + n int + } + tests := []struct { + name string + fields fields + args args + want string + wantErr bool + }{ + { + name: "Success Dice 1", + fields: fields{}, + args: args{n: 1}, + want: "凶", + wantErr: false, + }, + { + name: "Success Dice 2", + fields: fields{}, + args: args{n: 2}, + want: "吉", + wantErr: false, + }, + { + name: "Success Dice 4", + fields: fields{}, + args: args{n: 4}, + want: "中吉", + wantErr: false, + }, + { + name: "Success Dice 6", + fields: fields{}, + args: args{n: 6}, + want: "大吉", + wantErr: false, + }, + { + name: "Error Dice", + fields: fields{}, + args: args{n: 1000}, + want: "", + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + o := &omikuji.Omikuji{ + DateTime: tt.fields.DateTime, + Dice: tt.fields.Dice, + Result: tt.fields.Result, + } + got, err := omikuji.IntToStr(o, tt.args.n) + if (err != nil) != tt.wantErr { + t.Errorf("intToStr() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("intToStr() got = %v, want %v", got, tt.want) + } + }) + } +} + +func TestOmikuji_isNewYearHoliday(t *testing.T) { + type fields struct { + DateTime time.Time + Dice int + Result string + } + tests := []struct { + name string + fields fields + want bool + }{ + { + name: "Not New Year 1", + fields: fields{ + DateTime: time.Date(2020, time.July, 21, 5, 9, 23, 3424, time.UTC), + }, + want: false, + }, + { + name: "Not New Year 2", + fields: fields{ + DateTime: time.Date(2020, time.December, 31, 23, 59, 59, 123445, time.UTC), + }, + want: false, + }, + { + name: "New Year 1", + fields: fields{ + DateTime: time.Date(2020, time.January, 3, 6, 34, 55, 343424, time.UTC), + }, + want: true, + }, + { + name: "New Year 2", + fields: fields{ + DateTime: time.Date(2020, time.January, 1, 0, 00, 00, 343424, time.UTC), + }, + want: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + o := &omikuji.Omikuji{ + DateTime: tt.fields.DateTime, + Dice: tt.fields.Dice, + Result: tt.fields.Result, + } + if got := omikuji.IsNewYearHoliday(o); got != tt.want { + t.Errorf("isNewYearHoliday() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestOmikuji_throwOneToSix(t *testing.T) { + type fields struct { + DateTime time.Time + Dice int + Result string + } + tests := []struct { + name string + fields fields + want int + }{ + { + name: "Success", + fields: fields{}, + want: 6, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + o := &omikuji.Omikuji{ + DateTime: tt.fields.DateTime, + Dice: tt.fields.Dice, + Result: tt.fields.Result, + } + omikuji.StdMethods = omikuji.MockStdMethods + if got := omikuji.ThrowOneToSix(o); got != tt.want { + t.Errorf("throwOneToSix() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestRun(t *testing.T) { + type args struct { + port int + } + tests := []struct { + name string + args args + wantErr bool + mockMethod omikuji.StdLibProvider + }{ + { + name: "Error", + args: args{port: 8000}, + wantErr: true, + mockMethod: omikuji.StdLibProvider{ + HttpListenAndServe: func(addr string, handler http.Handler) error { + return fmt.Errorf("Mock http.listen and serve error") + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + omikuji.StdMethods = tt.mockMethod + if err := omikuji.Run(tt.args.port); (err != nil) != tt.wantErr { + t.Errorf("Run() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func Test_newOmikuji(t *testing.T) { + tests := []struct { + name string + want *omikuji.Omikuji + }{ + { + name: "Success", + want: &omikuji.Omikuji{}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := omikuji.NewOmikuji(); !reflect.DeepEqual(got, tt.want) { + t.Errorf("newOmikuji() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestOmikuji_tryOmikuji(t *testing.T) { + type fields struct { + DateTime time.Time + Dice int + Result string + } + tests := []struct { + name string + fields fields + wantErr bool + mockMethods omikuji.StdLibProvider + }{ + { + name: "New Year Holiday", + fields: fields{}, + wantErr: false, + mockMethods: omikuji.MockHoliday, + }, + { + name: "Not New Year Holiday", + fields: fields{}, + wantErr: false, + mockMethods: omikuji.MockNotHoliday, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + o := &omikuji.Omikuji{ + DateTime: tt.fields.DateTime, + Dice: tt.fields.Dice, + Result: tt.fields.Result, + } + omikuji.StdMethods = tt.mockMethods + if err := omikuji.TryOmikuji(o); (err != nil) != tt.wantErr { + t.Errorf("tryOmikuji() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestOmikuji_omikujiHandler(t *testing.T) { + type fields struct { + DateTime time.Time + Dice int + Result string + } + tests := []struct { + name string + fields fields + mockMethods omikuji.StdLibProvider + wantCode int + wantErr bool + wantResp string + }{ + { + name: "Not New Year", + fields: fields{ + DateTime: time.Date(2020, time.July, 21, 5, 9, 23, 3424, time.UTC), + Dice: 6, + Result: "大吉", + }, + mockMethods: omikuji.StdLibProvider{ + JsonMarshal: omikuji.StdStdMethods.JsonMarshal, + RandIntn: omikuji.StdStdMethods.RandIntn, + TimeNow: func() time.Time{ + return time.Date(2020, time.July, 21, 5, 9, 23, 3424, time.UTC) + }, + }, + wantResp: "{\"time\":\"2020-07-21T05:09:23.000003424Z\",\"dice\":3,\"result\":\"吉\"}", + wantCode: http.StatusOK, + wantErr: false, + }, + { + name: "Try Omikuji Error", + fields: fields{ + DateTime: time.Date(2020, time.July, 21, 5, 9, 23, 3424, time.UTC), + Dice: 6, + Result: "大吉", + }, + mockMethods: omikuji.StdLibProvider{ + JsonMarshal: func(v interface{}) ([]byte, error) { + return nil, fmt.Errorf("json.marshal dummy error...") + }, + RandIntn: omikuji.StdStdMethods.RandIntn, + TimeNow: omikuji.StdStdMethods.TimeNow, + }, + wantCode: http.StatusInternalServerError, + wantErr: true, + wantResp: "", + }, + { + name: "Try Omikuji Error", + fields: fields{ + DateTime: time.Date(2020, time.July, 21, 5, 9, 23, 3424, time.UTC), + Dice: 6, + Result: "大吉", + }, + mockMethods: omikuji.StdLibProvider{ + JsonMarshal: omikuji.StdMethods.JsonMarshal, + RandIntn: func(n int) int{ + return 10 + }, + TimeNow: omikuji.StdStdMethods.TimeNow, + }, + wantCode: http.StatusInternalServerError, + wantErr: true, + wantResp: "", + }, + + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + o := &omikuji.Omikuji{ + DateTime: tt.fields.DateTime, + Dice: tt.fields.Dice, + Result: tt.fields.Result, + } + w := httptest.NewRecorder() + r := httptest.NewRequest("GET", "/", nil) + omikuji.StdMethods = tt.mockMethods + omikuji.OmikujiHandler(o, w, r) + rw := w.Result() + defer rw.Body.Close() + if rw.StatusCode != tt.wantCode { + t.Fatal("unexpected status code") + } + b, err := ioutil.ReadAll(rw.Body) + if err != nil { + t.Fatal("unexpected error") + } + if rw.StatusCode == http.StatusOK{ + expected := tt.wantResp + if s := string(b); s != expected { + t.Fatalf("unexpected response: %s", s) + } + } + }) + } +} +