Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

【課題 4】おみくじAPIを作ってみよう #43

Open
wants to merge 12 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions kadai4/nas/fortune/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# CONST
BINARYNAME=fortune

export GO111MODULE=on

# command
.PHONY: help
help:
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'

.PHONY: build
build: ## go build
go build -v -o ${BINARYNAME}

#.PHONY: test
#test: ## go test
# go test -v -cover ./pkg/${BINARYNAME}

.PHONY: clean
clean: ## go clean
go clean -cache -testcache

.PHONY: analyze
analyze: ## do static code analysis
goimports -l -w .
go vet ./...
golint ./...

.PHONY: test
test: ## go test ...
go test -v -cover ./...

.PHONY: remove
remove: ## remove binary and test output data
rm -f ./${BINARYNAME}

.PHONY: all
all: remove clean test analyze build ## run 'build' with 'remove', 'clean', 'test' and 'analyze'
3 changes: 3 additions & 0 deletions kadai4/nas/fortune/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module github.com/gopherdojo/dojo7/kadai4/nas/fortune

go 1.13
40 changes: 40 additions & 0 deletions kadai4/nas/fortune/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package main

import (
"encoding/json"
"net/http"

"github.com/gopherdojo/dojo7/kadai4/nas/fortune/pkg/fortune"
)

var (
// MockDate is Date
MockDate fortune.Date
// MockParameter is Parameter
MockParameter fortune.Parameter
)

// FortuneHandler response fortune result json
func FortuneHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "applicaiton/json; charset=utf-8")

f := &fortune.Fortune{
Date: MockDate,
Parameter: MockParameter,
}

l, err := f.Draw()
if err != nil {
http.Error(w, "Server error", http.StatusInternalServerError)
return
}

enc := json.NewEncoder(w)
if err := enc.Encode(l); err != nil {
http.Error(w, "Server error", http.StatusInternalServerError)
}
}
func main() {
http.HandleFunc("/", FortuneHandler)
http.ListenAndServe(":8080", nil)
}
70 changes: 70 additions & 0 deletions kadai4/nas/fortune/main_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package main

import (
"io/ioutil"
"net/http"
"net/http/httptest"
"testing"
"time"

"github.com/gopherdojo/dojo7/kadai4/nas/fortune/pkg/fortune"
)

func TestFortuneHandler(t *testing.T) {
cases := []struct {
name string
date time.Time
parameter float64
want string
}{
{"OKGreat", time.Date(2019, 9, 25, 0, 0, 0, 0, time.FixedZone("Asia/Tokyo", 60*60*9)), 1, "{\"lack\":\"大吉\"}\n"},
{"OKHigh", time.Date(2019, 9, 25, 0, 0, 0, 0, time.FixedZone("Asia/Tokyo", 60*60*9)), 0.83, "{\"lack\":\"中吉\"}\n"},
{"OKMiddle", time.Date(2019, 9, 25, 0, 0, 0, 0, time.FixedZone("Asia/Tokyo", 60*60*9)), 0.5, "{\"lack\":\"吉\"}\n"},
{"OKLow", time.Date(2019, 9, 25, 0, 0, 0, 0, time.FixedZone("Asia/Tokyo", 60*60*9)), 0.16, "{\"lack\":\"凶\"}\n"},
{"OKNewYear", time.Date(2019, 1, 1, 0, 0, 0, 0, time.FixedZone("Asia/Tokyo", 60*60*9)), 0.00, "{\"lack\":\"大吉\"}\n"},
}
for _, tt := range cases {
t.Run(tt.name, func(t *testing.T) {
teardown := setupTest(t, fortune.DateFunc(func() time.Time { return tt.date }), fortune.ParameterFunc(func() float64 { return tt.parameter }))
defer teardown()
w := httptest.NewRecorder()
r := httptest.NewRequest("GET", "/", nil)
FortuneHandler(w, r)
rw := w.Result()
defer rw.Body.Close()
if rw.StatusCode != http.StatusOK {
t.Fatal("unexpected status code")
}
b, err := ioutil.ReadAll(rw.Body)
if err != nil {
t.Fatal("unexpected error")
}
if got := string(b); got != tt.want {
t.Errorf("unexpected response: %s, but want %s", got, tt.want)
}
})
}
}

func TestFortuneHandlerNG(t *testing.T) {
teardown := setupTest(t, fortune.DateFunc(func() time.Time { return time.Date(2019, 9, 25, 0, 0, 0, 0, time.FixedZone("Asia/Tokyo", 60*60*9)) }), fortune.ParameterFunc(func() float64 { return 1.01 }))
defer teardown()
w := httptest.NewRecorder()
r := httptest.NewRequest("GET", "/", nil)
FortuneHandler(w, r)
rw := w.Result()
defer rw.Body.Close()
if rw.StatusCode != http.StatusInternalServerError {
t.Errorf("unexpected status code : %d", rw.StatusCode)
}
}

func setupTest(t *testing.T, d fortune.Date, p fortune.Parameter) func() {
t.Helper()
MockDate = d
MockParameter = p
return func() {
MockDate = nil
MockParameter = nil
}
}
110 changes: 110 additions & 0 deletions kadai4/nas/fortune/pkg/fortune/fortune.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
package fortune

import (
"fmt"
"math/rand"
"time"
)

const (
// Great lack type
Great = "大吉"
// High lack type
High = "中吉"
// Middle lack type
Middle = "吉"
// Low lack type
Low = "凶"
)

// Date behave today
type Date interface {
Now() time.Time
}

// DateFunc return time func
type DateFunc func() time.Time

// Now return time
func (f DateFunc) Now() time.Time {
return f()
}

// Parameter behave random
type Parameter interface {
Float64() float64
}

// ParameterFunc return float64 func
type ParameterFunc func() float64

// Float64 return float64
func (f ParameterFunc) Float64() float64 {
return f()
}

// Fortune has several config
type Fortune struct {
Date
Parameter
}

// Lack is drew results
type Lack struct {
Lack string `json:"lack"`
}

// Draw return random Fortune
func (f *Fortune) Draw() (*Lack, error) {
d := today(f.Date)
if isNewYear(d) {
return &Lack{Great}, nil
}

p := random(f.Parameter)
l, err := check(p)
if err != nil {
return nil, err
}
return &Lack{l}, nil
}

func today(d Date) time.Time {
if d == nil {
return time.Now()
}
return d.Now()
}

func isNewYear(d time.Time) bool {
_, month, day := d.Date()
if month != time.January {
return false
}
return map[int]bool{
1: true,
2: true,
3: true,
}[day]
}

func random(p Parameter) float64 {
if p == nil {
return rand.Float64()
}
return p.Float64()
}

func check(p float64) (string, error) {
switch {
case 6.0/6.0 >= p && p > 5.0/6.0:
return Great, nil
case 5.0/6.0 >= p && p > 3.0/6.0:
return High, nil
case 3.0/6.0 >= p && p > 1.0/6.0:
return Middle, nil
case 1.0/6.0 >= p && p >= 0.0/6.0:
return Low, nil
}
return "", fmt.Errorf("Can't draw, please redraw")
}
115 changes: 115 additions & 0 deletions kadai4/nas/fortune/pkg/fortune/fortune_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
package fortune

import (
"reflect"
"testing"
"time"
)

func TestFortune_Draw(t *testing.T) {
cases := []struct {
name string
date time.Time
parameter float64
want *Lack
}{
{"Great", time.Date(2019, 9, 25, 0, 0, 0, 0, time.FixedZone("Asia/Tokyo", 60*60*9)), 1, &Lack{Great}},
{"NewYear", time.Date(2019, 1, 1, 0, 0, 0, 0, time.FixedZone("Asia/Tokyo", 60*60*9)), 0, &Lack{Great}},
}

for _, tt := range cases {
t.Run(tt.name, func(t *testing.T) {
f := &Fortune{
Date: DateFunc(func() time.Time { return tt.date }),
Parameter: ParameterFunc(func() float64 { return tt.parameter }),
}
got, err := f.Draw()
if err != nil {
t.Errorf("Unexpected Error : %v", err)
}
if !reflect.DeepEqual(tt.want, got) {
t.Errorf("Draw() => want %s, but got %s", tt.want, got)
}
})
}
}

func TestFortune_DrawInValid(t *testing.T) {
test := struct {
date time.Time
parameter float64
want *Lack
}{time.Date(2019, 9, 25, 0, 0, 0, 0, time.FixedZone("Asia/Tokyo", 60*60*9)), 1.01, nil}

f := &Fortune{
Date: DateFunc(func() time.Time { return test.date }),
Parameter: ParameterFunc(func() float64 { return test.parameter }),
}
got, err := f.Draw()
if err == nil {
t.Error("Expected Error but nil")
}
if test.want != got {
t.Errorf("Draw() => want%s, but got %s", test.want, got)
}
}

func TestIsNewYear(t *testing.T) {
cases := []struct {
name string
date time.Time
want bool
}{
{"True", time.Date(2019, 1, 1, 0, 0, 0, 0, time.FixedZone("Asia/Tokyo", 60*60*9)), true},
{"FalseMonth", time.Date(2019, 2, 1, 0, 0, 0, 0, time.FixedZone("Asia/Tokyo", 60*60*9)), false},
{"FalseDay", time.Date(2019, 1, 4, 0, 0, 0, 0, time.FixedZone("Asia/Tokyo", 60*60*9)), false},
}

for _, tt := range cases {
t.Run(tt.name, func(t *testing.T) {
if got := isNewYear(tt.date); tt.want != got {
t.Errorf("isNewYear(%v) => want %t, but got %t", tt.date, tt.want, got)
}
})
}
}

func TestCheck(t *testing.T) {
cases := []struct {
name string
parameter float64
want string
}{
{"Low", 0.16, Low},
{"Middle", 0.5, Middle},
{"High", 0.83, High},
{"Great", 1, Great},
}

for _, tt := range cases {
t.Run(tt.name, func(t *testing.T) {
got, err := check(tt.parameter)
if err != nil {
t.Errorf("Unexpected Error : %v", err)
}
if tt.want != got {
t.Errorf("Draw() => want %s, but got %s", tt.want, got)
}
})
}
}

func TestCheckInValid(t *testing.T) {
test := struct {
parameter float64
want string
}{1.01, ""}

got, err := check(test.parameter)
if err == nil {
t.Error("Expected Error but nil")
}
if test.want != got {
t.Errorf("check() => want %s, but got %s", test.want, got)
}
}