mirror of
https://github.com/gabehf/trivia-api.git
synced 2026-03-07 21:48:15 -08:00
First
This commit is contained in:
commit
a9cc5a8aad
15 changed files with 1640 additions and 0 deletions
50
server/get_guess.go
Normal file
50
server/get_guess.go
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
package server
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
|
||||
type GetGuessRequest struct {
|
||||
QuestionId string `json:"question_id" query:"question_id"`
|
||||
Guess string `json:"guess" query:"guess"`
|
||||
}
|
||||
type GetGuessResponse struct {
|
||||
QuestionId string `json:"question_id"`
|
||||
Correct bool `json:"correct"`
|
||||
}
|
||||
|
||||
func (s *Server) GetGuess(e echo.Context) error {
|
||||
req := new(GetGuessRequest)
|
||||
e.Bind(req)
|
||||
|
||||
// ensure required parameters exist
|
||||
errs := make(map[string]string, 0)
|
||||
if req.Guess == "" {
|
||||
errs["guess"] = "required parameter missing"
|
||||
}
|
||||
if req.QuestionId == "" {
|
||||
errs["question_id"] = "required parameter missing"
|
||||
}
|
||||
if len(errs) > 0 {
|
||||
return e.JSON(400, &ErrorResponse{
|
||||
Error: true,
|
||||
Data: errs,
|
||||
})
|
||||
}
|
||||
|
||||
question := s.Q.GetQuestionById(req.QuestionId)
|
||||
if question == nil {
|
||||
errs["question_id"] = "invalid or malformed"
|
||||
return e.JSON(404, &ErrorResponse{
|
||||
Error: true,
|
||||
Data: errs,
|
||||
})
|
||||
}
|
||||
|
||||
// validate answer with case insensitive string compare
|
||||
correct := strings.EqualFold(question.Answer, req.Guess)
|
||||
|
||||
return e.JSONPretty(200, &GetGuessResponse{req.QuestionId, correct}, " ")
|
||||
}
|
||||
127
server/get_guess_test.go
Normal file
127
server/get_guess_test.go
Normal file
|
|
@ -0,0 +1,127 @@
|
|||
package server_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/gabehf/trivia-api/server"
|
||||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
|
||||
func TestGuessHandler(t *testing.T) {
|
||||
jsonBody := []byte("{\"question_id\":\"World History|0\",\"guess\":\"seven\"}")
|
||||
|
||||
// OK path: json body
|
||||
e := echo.New()
|
||||
req := httptest.NewRequest("GET", "/guess", bytes.NewReader(jsonBody))
|
||||
req.Header["Content-Type"] = []string{"application/json"}
|
||||
res := httptest.NewRecorder()
|
||||
c := e.NewContext(req, res)
|
||||
err := S.GetGuess(c)
|
||||
if err != nil {
|
||||
t.Errorf("expected nil error, got %v", err)
|
||||
}
|
||||
if res.Code != http.StatusOK {
|
||||
t.Errorf("expected status 200 OK, got %d", res.Code)
|
||||
}
|
||||
|
||||
result := new(server.GetGuessResponse)
|
||||
err = json.Unmarshal(res.Body.Bytes(), result)
|
||||
if err != nil {
|
||||
t.Error("malformed json response")
|
||||
}
|
||||
if result.QuestionId != "World History|0" {
|
||||
t.Errorf("expected question_id 'World History|0', got '%s'", result.QuestionId)
|
||||
}
|
||||
if result.Correct != true {
|
||||
t.Errorf("expected correct to be true, got false")
|
||||
}
|
||||
|
||||
// OK path: urlencoded body
|
||||
e = echo.New()
|
||||
req = httptest.NewRequest("GET", "/guess?question_id=World+History%7C0&guess=Seven", nil)
|
||||
req.Header["Content-Type"] = []string{"application/x-www-form-urlencoded"}
|
||||
res = httptest.NewRecorder()
|
||||
c = e.NewContext(req, res)
|
||||
err = S.GetGuess(c)
|
||||
if err != nil {
|
||||
t.Errorf("expected nil error, got %v", err)
|
||||
}
|
||||
if res.Code != http.StatusOK {
|
||||
t.Errorf("expected status 200 OK, got %d", res.Code)
|
||||
}
|
||||
|
||||
result = new(server.GetGuessResponse)
|
||||
err = json.Unmarshal(res.Body.Bytes(), result)
|
||||
if err != nil {
|
||||
t.Error("malformed json response")
|
||||
}
|
||||
if result.QuestionId != "World History|0" {
|
||||
t.Errorf("expected question_id 'World History|0', got '%s'", result.QuestionId)
|
||||
}
|
||||
if result.Correct != true {
|
||||
t.Errorf("expected correct to be true, got false")
|
||||
}
|
||||
|
||||
// FAIL path: invalid question id
|
||||
e = echo.New()
|
||||
req = httptest.NewRequest("GET", "/guess?question_id=hey&guess=Seven", nil)
|
||||
req.Header["Content-Type"] = []string{"application/x-www-form-urlencoded"}
|
||||
res = httptest.NewRecorder()
|
||||
c = e.NewContext(req, res)
|
||||
err = S.GetGuess(c)
|
||||
if err != nil {
|
||||
t.Errorf("expected nil error, got %v", err)
|
||||
}
|
||||
if res.Code != http.StatusNotFound {
|
||||
t.Errorf("expected status 400 Bad Request, got %d", res.Code)
|
||||
}
|
||||
errResult := struct {
|
||||
Error bool
|
||||
Data map[string]string
|
||||
}{}
|
||||
err = json.Unmarshal(res.Body.Bytes(), &errResult)
|
||||
if err != nil {
|
||||
t.Error("malformed json response")
|
||||
}
|
||||
if !errResult.Error {
|
||||
t.Error("expected error to be true, got false")
|
||||
}
|
||||
if errResult.Data["question_id"] == "" {
|
||||
t.Errorf("expected error information in data[question_id], got \"\"")
|
||||
}
|
||||
|
||||
// FAIL path: missing params
|
||||
e = echo.New()
|
||||
req = httptest.NewRequest("GET", "/guess", nil)
|
||||
req.Header["Content-Type"] = []string{"application/x-www-form-urlencoded"}
|
||||
res = httptest.NewRecorder()
|
||||
c = e.NewContext(req, res)
|
||||
err = S.GetGuess(c)
|
||||
if err != nil {
|
||||
t.Errorf("expected nil error, got %v", err)
|
||||
}
|
||||
if res.Code != http.StatusBadRequest {
|
||||
t.Errorf("expected status 400 Bad Request, got %d", res.Code)
|
||||
}
|
||||
errResult = struct {
|
||||
Error bool
|
||||
Data map[string]string
|
||||
}{}
|
||||
err = json.Unmarshal(res.Body.Bytes(), &errResult)
|
||||
if err != nil {
|
||||
t.Error("malformed json response")
|
||||
}
|
||||
if !errResult.Error {
|
||||
t.Error("expected error to be true, got false")
|
||||
}
|
||||
if errResult.Data["question_id"] == "" {
|
||||
t.Errorf("expected error information in data[question_id], got \"\"")
|
||||
}
|
||||
if errResult.Data["guess"] == "" {
|
||||
t.Errorf("expected error information in data[guess], got \"\"")
|
||||
}
|
||||
}
|
||||
71
server/get_trivia.go
Normal file
71
server/get_trivia.go
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
package server
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"strconv"
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
|
||||
type GetTriviaRequest struct {
|
||||
Category string `json:"category" query:"category"`
|
||||
}
|
||||
type GetTriviaResponse struct {
|
||||
QuestionId string `json:"question_id"`
|
||||
Question string `json:"question"`
|
||||
Category string `json:"category"`
|
||||
Format string `json:"format"`
|
||||
Choices map[string]string `json:"choices,omitempty"`
|
||||
}
|
||||
|
||||
type ErrorResponse struct {
|
||||
Error bool `json:"error"`
|
||||
Data map[string]string `json:"data,omitempty"`
|
||||
Message string `json:"message,omitempty"`
|
||||
}
|
||||
|
||||
func (s *Server) GetTrivia(e echo.Context) error {
|
||||
req := new(GetTriviaRequest)
|
||||
e.Bind(req)
|
||||
|
||||
question, qIndex := s.Q.GetRandomQuestion(req.Category)
|
||||
if question == nil {
|
||||
return e.JSON(404, &ErrorResponse{
|
||||
Error: true,
|
||||
Data: map[string]string{
|
||||
"category": "category is invalid",
|
||||
},
|
||||
})
|
||||
}
|
||||
// randomly order answer choices if the format is multiple choice
|
||||
if question.Format == "MultipleChoice" && question.Choices != nil {
|
||||
rand.Shuffle(len(question.Choices), func(i, j int) {
|
||||
question.Choices[i], question.Choices[j] = question.Choices[j], question.Choices[i]
|
||||
})
|
||||
// enforce that multiple choice questions must have four choices
|
||||
// if not, there must be an error in our data somewhere that we need
|
||||
// to fix
|
||||
if len(question.Choices) != 4 {
|
||||
return e.JSON(500, &ErrorResponse{
|
||||
Error: true,
|
||||
Message: "internal server error",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// build and return response
|
||||
tq := new(GetTriviaResponse)
|
||||
tq.QuestionId = question.Category + "|" + strconv.Itoa(qIndex)
|
||||
tq.Category = question.Category
|
||||
tq.Format = question.Format
|
||||
tq.Question = question.Question
|
||||
if tq.Format == "MultipleChoice" {
|
||||
tq.Choices = map[string]string{
|
||||
"A": question.Choices[0],
|
||||
"B": question.Choices[1],
|
||||
"C": question.Choices[2],
|
||||
"D": question.Choices[3],
|
||||
}
|
||||
}
|
||||
return e.JSONPretty(200, tq, " ")
|
||||
}
|
||||
145
server/get_trivia_test.go
Normal file
145
server/get_trivia_test.go
Normal file
|
|
@ -0,0 +1,145 @@
|
|||
package server_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/gabehf/trivia-api/server"
|
||||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
|
||||
func TestTriviaHandler(t *testing.T) {
|
||||
jsonBody := []byte("{\"category\":\"World History\"}")
|
||||
|
||||
expect := server.GetTriviaResponse{
|
||||
Question: "The ancient city of Rome was built on how many hills?",
|
||||
Format: "MultipleChoice",
|
||||
Category: "World History",
|
||||
}
|
||||
|
||||
// OK path: json body
|
||||
e := echo.New()
|
||||
req := httptest.NewRequest("GET", "/trivia", bytes.NewReader(jsonBody))
|
||||
req.Header["Content-Type"] = []string{"application/json"}
|
||||
res := httptest.NewRecorder()
|
||||
c := e.NewContext(req, res)
|
||||
err := S.GetTrivia(c)
|
||||
if err != nil {
|
||||
t.Errorf("expected nil error, got %v", err)
|
||||
}
|
||||
if res.Code != http.StatusOK {
|
||||
t.Errorf("expected status 200 OK, got %d", res.Code)
|
||||
}
|
||||
|
||||
result := new(server.GetTriviaResponse)
|
||||
err = json.Unmarshal(res.Body.Bytes(), result)
|
||||
if err != nil {
|
||||
t.Error("malformed json response")
|
||||
}
|
||||
if result.Question != expect.Question {
|
||||
t.Errorf("expected question '%s', got '%s'", expect.Question, result.Question)
|
||||
}
|
||||
if result.Format != expect.Format {
|
||||
t.Errorf("expected format %s, got %s", expect.Format, result.Format)
|
||||
}
|
||||
if !strings.EqualFold(expect.Category, result.Category) {
|
||||
t.Errorf("expected category %s, got %s", expect.Category, result.Category)
|
||||
}
|
||||
|
||||
// OK path: urlencoded body
|
||||
e = echo.New()
|
||||
req = httptest.NewRequest("GET", "/trivia?category=World+History", nil)
|
||||
req.Header["Content-Type"] = []string{"application/x-www-form-urlencoded"}
|
||||
res = httptest.NewRecorder()
|
||||
c = e.NewContext(req, res)
|
||||
err = S.GetTrivia(c)
|
||||
if err != nil {
|
||||
t.Errorf("expected nil error, got %v", err)
|
||||
}
|
||||
if res.Code != http.StatusOK {
|
||||
t.Errorf("expected status 200 OK, got %d", res.Code)
|
||||
}
|
||||
expect = server.GetTriviaResponse{
|
||||
Question: "The ancient city of Rome was built on how many hills?",
|
||||
Format: "MultipleChoice",
|
||||
Category: "World History",
|
||||
}
|
||||
result = new(server.GetTriviaResponse)
|
||||
err = json.Unmarshal(res.Body.Bytes(), result)
|
||||
if err != nil {
|
||||
t.Error("malformed json response")
|
||||
}
|
||||
if result.Question != expect.Question {
|
||||
t.Errorf("expected question '%s', got '%s'", expect.Question, result.Question)
|
||||
}
|
||||
if result.Format != expect.Format {
|
||||
t.Errorf("expected format %s, got %s", expect.Format, result.Format)
|
||||
}
|
||||
if !strings.EqualFold(expect.Category, result.Category) {
|
||||
t.Errorf("expected category %s, got %s", expect.Category, result.Category)
|
||||
}
|
||||
|
||||
// OK path: no body (random category)
|
||||
e = echo.New()
|
||||
req = httptest.NewRequest("GET", "/trivia", nil)
|
||||
res = httptest.NewRecorder()
|
||||
c = e.NewContext(req, res)
|
||||
err = S.GetTrivia(c)
|
||||
if err != nil {
|
||||
t.Errorf("expected nil error, got %v", err)
|
||||
}
|
||||
if res.Code != http.StatusOK {
|
||||
t.Errorf("expected status 200 OK, got %d", res.Code)
|
||||
}
|
||||
expect = server.GetTriviaResponse{
|
||||
Question: "The ancient city of Rome was built on how many hills?",
|
||||
Format: "MultipleChoice",
|
||||
Category: "World History",
|
||||
}
|
||||
result = new(server.GetTriviaResponse)
|
||||
err = json.Unmarshal(res.Body.Bytes(), result)
|
||||
if err != nil {
|
||||
t.Error("malformed json response")
|
||||
}
|
||||
if result.Question != expect.Question {
|
||||
t.Errorf("expected question '%s', got '%s'", expect.Question, result.Question)
|
||||
}
|
||||
if result.Format != expect.Format {
|
||||
t.Errorf("expected format %s, got %s", expect.Format, result.Format)
|
||||
}
|
||||
if !strings.EqualFold(expect.Category, result.Category) {
|
||||
t.Errorf("expected category %s, got %s", expect.Category, result.Category)
|
||||
}
|
||||
|
||||
// FAIL path: invalid category
|
||||
e = echo.New()
|
||||
req = httptest.NewRequest("GET", "/trivia?category=70s+Music", nil)
|
||||
req.Header["Content-Type"] = []string{"application/x-www-form-urlencoded"}
|
||||
res = httptest.NewRecorder()
|
||||
c = e.NewContext(req, res)
|
||||
err = S.GetTrivia(c)
|
||||
if err != nil {
|
||||
t.Errorf("expected nil error, got %v", err)
|
||||
}
|
||||
if res.Code != http.StatusNotFound {
|
||||
t.Errorf("expected status 404 Not Found, got %d", res.Code)
|
||||
}
|
||||
errResult := struct {
|
||||
Error bool
|
||||
Data map[string]string
|
||||
}{}
|
||||
err = json.Unmarshal(res.Body.Bytes(), &errResult)
|
||||
if err != nil {
|
||||
t.Error("malformed json response")
|
||||
}
|
||||
if !errResult.Error {
|
||||
t.Error("expected error to be true, got false")
|
||||
}
|
||||
if errResult.Data["category"] == "" {
|
||||
t.Errorf("expected error information in data[category], got \"\"")
|
||||
}
|
||||
}
|
||||
44
server/server.go
Normal file
44
server/server.go
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
package server
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/gabehf/trivia-api/trivia"
|
||||
"github.com/labstack/echo/v4"
|
||||
"github.com/labstack/echo/v4/middleware"
|
||||
)
|
||||
|
||||
type Server struct {
|
||||
Q *trivia.Questions
|
||||
}
|
||||
|
||||
func (s *Server) Init() {
|
||||
s.Q = new(trivia.Questions)
|
||||
s.Q.Init()
|
||||
}
|
||||
|
||||
func Run() error {
|
||||
// init server struct
|
||||
s := new(Server)
|
||||
s.Init()
|
||||
|
||||
// load trivia data
|
||||
file, err := os.Open("trivia.json")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
err = s.Q.Load(file)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// create router and mount handlers
|
||||
e := echo.New()
|
||||
e.Use(middleware.Logger())
|
||||
e.Use(middleware.Recover())
|
||||
e.GET("/trivia", s.GetTrivia)
|
||||
e.GET("/guess", s.GetGuess)
|
||||
|
||||
// start listening
|
||||
return e.Start(":3000")
|
||||
}
|
||||
34
server/server_main_test.go
Normal file
34
server/server_main_test.go
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
package server_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/gabehf/trivia-api/server"
|
||||
"github.com/gabehf/trivia-api/trivia"
|
||||
)
|
||||
|
||||
var S *server.Server
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
S = new(server.Server)
|
||||
S.Init()
|
||||
S.Q.Init()
|
||||
S.Q.Categories = []string{"world history"}
|
||||
S.Q.M = map[string][]trivia.Question{
|
||||
"world history": {
|
||||
{
|
||||
Question: "The ancient city of Rome was built on how many hills?",
|
||||
Format: "MultipleChoice",
|
||||
Category: "World History",
|
||||
Choices: []string{
|
||||
"Eight",
|
||||
"Four",
|
||||
"Nine",
|
||||
"Seven",
|
||||
},
|
||||
Answer: "Seven",
|
||||
},
|
||||
},
|
||||
}
|
||||
m.Run()
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue