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

Kadai4 hioki-daichi #65

Open
wants to merge 12 commits into
base: master
Choose a base branch
from
Open

Kadai4 hioki-daichi #65

wants to merge 12 commits into from

Conversation

hioki-daichi
Copy link

@hioki-daichi hioki-daichi commented Sep 12, 2018

おみくじ API

おみくじAPIを作ってみよう

  • JSON形式でおみくじの結果を返す
  • 正月(1/1-1/3)だけ大吉にする
  • ハンドラのテストを書いてみる

JSON形式でおみくじの結果を返す

返すようにしました。

$ curl -s localhost:8080 | jq .
{
  "name": "Gopher",
  "fortune": ""
}

レスポンスヘッダとして Content-Type: application/json; charset=utf-8 も返すようにしています。

$ curl -I 'localhost:8080/?name=hioki%20daichi'
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
X-Content-Type-Options: nosniff
Date: Wed, 12 Sep 2018 14:35:41 GMT
Content-Length: 43

正月(1/1-1/3)だけ大吉にする

現在の日付が 1/1-1/3 の場合に true を返すメソッド datehelper.IsDuringTheNewYear() を作成し、

// IsDuringTheNewYear returns whether the current date is the New Year or not.
func IsDuringTheNewYear() bool {
loc, err := loadLocationFunc("Asia/Tokyo")
if err != nil {
panic(err)
}
_, month, day := nowFunc().In(loc).Date()
if month == time.January && (day == 1 || day == 2 || day == 3) {
return true
}
return false
}

func TestDatehelper_IsDuringTheNewYear(t *testing.T) {
loc, err := time.LoadLocation("Asia/Tokyo")
if err != nil {
t.Fatalf("err %s", err)
}
cases := map[string]struct {
year int
month time.Month
day int
expected bool
}{
"2018-12-31": {year: 2018, month: time.December, day: 31, expected: false},
"2019-01-01": {year: 2019, month: time.January, day: 1, expected: true},
"2019-01-02": {year: 2019, month: time.January, day: 2, expected: true},
"2019-01-03": {year: 2019, month: time.January, day: 3, expected: true},
"2019-01-04": {year: 2019, month: time.January, day: 4, expected: false},
}
for n, c := range cases {
c := c
t.Run(n, func(t *testing.T) {
nowFunc = func() time.Time {
return time.Date(c.year, c.month, c.day, 0, 0, 0, 0, loc)
}
expected := c.expected
actual := IsDuringTheNewYear()
if actual != expected {
t.Errorf(`expected: "%t" actual: "%t"`, expected, actual)
}
})
}
}

main からそのメソッドを呼び、返り値が true の場合は大吉を返すようにしました。

if isDuringTheNewYearFunc() {
ftn = fortune.Daikichi
} else {
ftn = fortune.DrawFortune()
}

ハンドラのテストを書いてみる

以下に書きました。

func TestMain_handler_StatusCode(t *testing.T) {
w := httptest.NewRecorder()
req := httptest.NewRequest("GET", "/", nil)
handler(w, req)
rw := w.Result()
defer rw.Body.Close()
expected := http.StatusOK
actual := rw.StatusCode
if actual != expected {
t.Errorf(`unexpected status code: expected: "%d" actual: "%d"`, expected, actual)
}
}
func TestMain_handler_ResponseBody(t *testing.T) {
cases := map[string]struct {
seed int64
nameParam string
expected string
}{
"KYOU": {seed: 0, nameParam: "", expected: "{\"name\":\"Gopher\",\"fortune\":\"\"}\n"},
"DAIKYOU": {seed: 1, nameParam: "", expected: "{\"name\":\"Gopher\",\"fortune\":\"大凶\"}\n"},
"SUEKICHI": {seed: 2, nameParam: "", expected: "{\"name\":\"Gopher\",\"fortune\":\"末吉\"}\n"},
"KICHI": {seed: 3, nameParam: "", expected: "{\"name\":\"Gopher\",\"fortune\":\"\"}\n"},
"CHUKICHI": {seed: 4, nameParam: "", expected: "{\"name\":\"Gopher\",\"fortune\":\"中吉\"}\n"},
"SHOKICHI": {seed: 5, nameParam: "", expected: "{\"name\":\"Gopher\",\"fortune\":\"小吉\"}\n"},
"DAICHIKI": {seed: 9, nameParam: "", expected: "{\"name\":\"Gopher\",\"fortune\":\"大吉\"}\n"},
"name param": {seed: 9, nameParam: "hioki-daichi", expected: "{\"name\":\"hioki-daichi\",\"fortune\":\"大吉\"}\n"},
}
for n, c := range cases {
c := c
t.Run(n, func(t *testing.T) {
rand.Seed(c.seed)
w := httptest.NewRecorder()
req := httptest.NewRequest("GET", "/", nil)
if c.nameParam != "" {
q := req.URL.Query()
q.Add("name", c.nameParam)
req.URL.RawQuery = q.Encode()
}
handler(w, req)
rw := w.Result()
defer rw.Body.Close()
b, err := ioutil.ReadAll(rw.Body)
if err != nil {
t.Fatalf("err %s", err)
}
expected := c.expected
actual := string(b)
if actual != expected {
t.Errorf(`unexpected response body: expected: "%s" actual: "%s"`, expected, actual)
}
})
}
}
func TestMain_handler_DuringTheNewYear(t *testing.T) {
isDuringTheNewYearFunc = func() bool {
return true
}
w := httptest.NewRecorder()
req := httptest.NewRequest("GET", "/", nil)
handler(w, req)
rw := w.Result()
defer rw.Body.Close()
b, err := ioutil.ReadAll(rw.Body)
if err != nil {
t.Fatalf("err %s", err)
}
expected := "{\"name\":\"Gopher\",\"fortune\":\"大吉\"}\n"
actual := string(b)
if actual != expected {
t.Errorf(`unexpected response body: expected: "%s" actual: "%s"`, expected, actual)
}
}
func TestMain_handler_ValidationError(t *testing.T) {
w := httptest.NewRecorder()
req := httptest.NewRequest("GET", "/", nil)
q := req.URL.Query()
q.Add("name", "123456789012345678901234567890123")
req.URL.RawQuery = q.Encode()
handler(w, req)
rw := w.Result()
defer rw.Body.Close()
b, err := ioutil.ReadAll(rw.Body)
if err != nil {
t.Fatalf("err %s", err)
}
if rw.StatusCode != http.StatusBadRequest {
t.Errorf(`unexpected status code: expected: %d actual: %d`, http.StatusBadRequest, rw.StatusCode)
}
expected := "{\"errors\":[\"Name is too long (maximum is 32 characters)\"]}\n"
actual := string(b)
if actual != expected {
t.Errorf(`unexpected response body: expected: "%s" actual: "%s"`, expected, actual)
}
}
func TestMain_handler_ToJSONError(t *testing.T) {
toJSONFunc = func(v interface{}) (string, error) {
return "", errors.New("error in TestMain_handler_ToJSONError")
}
w := httptest.NewRecorder()
req := httptest.NewRequest("GET", "/", nil)
handler(w, req)
rw := w.Result()
defer rw.Body.Close()
b, err := ioutil.ReadAll(rw.Body)
if err != nil {
t.Fatalf("err %s", err)
}
expected := "Internal Server Error\n"
actual := string(b)
if actual != expected {
t.Errorf(`unexpected response body: expected: "%s" actual: "%s"`, expected, actual)
}
}

補足

  • README はこちらです。
  • カバレッジはお手元の環境で以下を実行することで確認できます。
    • curl -s https://raw.githubusercontent.com/gopherdojo/dojo3/kadai4-hioki-daichi/kadai4/hioki-daichi/coverage.html -o /tmp/coverage-hioki-daichi.html && open /tmp/coverage-hioki-daichi.html

工夫点

name クエリパラメータ

指定した name クエリパラメータの値を JSON の name メンバーとして返すようにしました。

$ curl -s 'localhost:8080/?name=hioki-daichi' | jq .
{
  "name": "hioki-daichi",
  "fortune": "大吉"
}

33 文字以上指定された場合はバリデーションエラーとし、

$ curl -s 'localhost:8080/?name=A%20name%20longer%20than%20thirty%20two%20characters' | jq .
{
  "errors": [
    "Name is too long (maximum is 32 characters)"
  ]
}

ステータスコード 400 で返すようにしてみました。

$ curl -I 'localhost:8080/?name=A%20name%20longer%20than%20thirty%20two%20characters'
HTTP/1.1 400 Bad Request
Content-Type: application/json; charset=utf-8
X-Content-Type-Options: nosniff
Date: Wed, 12 Sep 2018 14:41:25 GMT
Content-Length: 59

@hioki-daichi hioki-daichi self-assigned this Sep 12, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant