Update quiz
This commit is contained in:
parent
405113e639
commit
ee45c1ba9c
10 changed files with 205 additions and 42 deletions
|
@ -7,6 +7,20 @@ type Response struct {
|
|||
Content interface{} `json:"content"`
|
||||
}
|
||||
|
||||
type Question struct {
|
||||
Text string `json:"text"`
|
||||
}
|
||||
|
||||
type Answer struct {
|
||||
Text string
|
||||
Correct bool
|
||||
}
|
||||
|
||||
type Quiz struct {
|
||||
Question *Question `json:"question"`
|
||||
Answers []*Answer `json:"answers"`
|
||||
}
|
||||
|
||||
type ReadAllQuizResponse struct {
|
||||
Status string `json:"status"`
|
||||
Content []*models.Quiz `json:"content"`
|
||||
|
@ -17,16 +31,24 @@ type CreateQuizResponse struct {
|
|||
Content *models.Quiz `json:"content"`
|
||||
}
|
||||
|
||||
type UpdateQuizResponse struct {
|
||||
Status string `json:"status"`
|
||||
Content *models.Quiz `json:"content"`
|
||||
}
|
||||
|
||||
type CreateQuestionRequest struct {
|
||||
Text string `json:"text"`
|
||||
*Question
|
||||
}
|
||||
|
||||
type CreateAnswerRequest struct {
|
||||
Text string
|
||||
Correct bool
|
||||
*Answer
|
||||
}
|
||||
|
||||
type CreateQuizRequest struct {
|
||||
Question *CreateQuestionRequest `json:"question"`
|
||||
Answers []*CreateAnswerRequest `json:"answers"`
|
||||
*Quiz
|
||||
}
|
||||
|
||||
type UpdateQuizRequest struct {
|
||||
ID string
|
||||
*Quiz
|
||||
}
|
||||
|
|
1
go.mod
1
go.mod
|
@ -6,6 +6,7 @@ require github.com/sirupsen/logrus v1.8.1
|
|||
|
||||
require (
|
||||
github.com/google/uuid v1.3.0 // indirect
|
||||
github.com/julienschmidt/httprouter v1.3.0 // indirect
|
||||
github.com/kr/pretty v0.2.1 // indirect
|
||||
github.com/kr/text v0.1.0 // indirect
|
||||
github.com/remogatto/prettytest v0.0.0-20200211072524-6d385e11dcb8 // indirect
|
||||
|
|
2
go.sum
2
go.sum
|
@ -1,6 +1,8 @@
|
|||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U=
|
||||
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
|
||||
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
|
||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
|
|
|
@ -11,15 +11,15 @@ type Hasher interface {
|
|||
// hash calculated from the question using
|
||||
// QuestionHash. Following hashes are calculated from the
|
||||
// answers using AnswerHash.
|
||||
QuizHashes(quiz *client.CreateQuizRequest) []string
|
||||
QuizHashes(quiz *client.Quiz) []string
|
||||
|
||||
// QuestionHash returns an hash calculated from a field of
|
||||
// Question struct.
|
||||
QuestionHash(question *client.CreateQuestionRequest) string
|
||||
QuestionHash(question *client.Question) string
|
||||
|
||||
// AnswerHash returns an hash calculated from a field of
|
||||
// Answer struct.
|
||||
AnswerHash(answer *client.CreateAnswerRequest) string
|
||||
AnswerHash(answer *client.Answer) string
|
||||
|
||||
// Calculate calculates a checksum from all the given hashes.
|
||||
Calculate(hashes []string) string
|
||||
|
|
|
@ -22,7 +22,7 @@ func NewDefault256Hasher(hashFn hasher.HashFunc) *Default256Hasher {
|
|||
return &Default256Hasher{hashFn}
|
||||
}
|
||||
|
||||
func (h *Default256Hasher) QuizHashes(quiz *client.CreateQuizRequest) []string {
|
||||
func (h *Default256Hasher) QuizHashes(quiz *client.Quiz) []string {
|
||||
result := make([]string, 0)
|
||||
|
||||
result = append(result, h.QuestionHash(quiz.Question))
|
||||
|
@ -36,11 +36,11 @@ func (h *Default256Hasher) QuizHashes(quiz *client.CreateQuizRequest) []string {
|
|||
return result
|
||||
}
|
||||
|
||||
func (h *Default256Hasher) QuestionHash(question *client.CreateQuestionRequest) string {
|
||||
func (h *Default256Hasher) QuestionHash(question *client.Question) string {
|
||||
return h.hashFn(question.Text)
|
||||
}
|
||||
|
||||
func (h *Default256Hasher) AnswerHash(answer *client.CreateAnswerRequest) string {
|
||||
func (h *Default256Hasher) AnswerHash(answer *client.Answer) string {
|
||||
return h.hashFn(answer.Text)
|
||||
}
|
||||
|
||||
|
|
51
server.go
51
server.go
|
@ -9,6 +9,7 @@ import (
|
|||
"git.andreafazzi.eu/andrea/probo/logger"
|
||||
"git.andreafazzi.eu/andrea/probo/models"
|
||||
"git.andreafazzi.eu/andrea/probo/store"
|
||||
"github.com/julienschmidt/httprouter"
|
||||
)
|
||||
|
||||
const jsonContentType = "application/json"
|
||||
|
@ -22,22 +23,24 @@ func NewQuizHubCollectorServer(store store.QuizHubCollectorStore) *QuizHubCollec
|
|||
ps := new(QuizHubCollectorServer)
|
||||
ps.store = store
|
||||
|
||||
router := http.NewServeMux()
|
||||
router := httprouter.New()
|
||||
|
||||
router.Handle("/quizzes", logger.WithLogging(http.HandlerFunc(ps.testHandler)))
|
||||
router.GET("/quizzes", httprouter.Handle(ps.readAllQuizzesHandler))
|
||||
router.POST("/quizzes/create", httprouter.Handle(ps.createQuizHandler))
|
||||
router.POST("/quizzes/:id/update", httprouter.Handle(ps.updateQuizHandler))
|
||||
// router.Handle("/quizzes", logger.WithLogging(http.HandlerFunc(ps.testHandler)))
|
||||
|
||||
ps.Handler = router
|
||||
ps.Handler = logger.WithLogging(router)
|
||||
|
||||
return ps
|
||||
}
|
||||
|
||||
func (ps *QuizHubCollectorServer) testHandler(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.Method {
|
||||
case http.MethodGet:
|
||||
func (ps *QuizHubCollectorServer) readAllQuizzesHandler(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
|
||||
w.Header().Set("content-type", jsonContentType)
|
||||
json.NewEncoder(w).Encode(ps.readAllQuizzes(w, r))
|
||||
}
|
||||
|
||||
case http.MethodPost:
|
||||
func (ps *QuizHubCollectorServer) createQuizHandler(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
|
||||
response := new(client.Response)
|
||||
|
||||
quiz, err := ps.createQuiz(w, r)
|
||||
|
@ -49,7 +52,20 @@ func (ps *QuizHubCollectorServer) testHandler(w http.ResponseWriter, r *http.Req
|
|||
|
||||
w.WriteHeader(http.StatusAccepted)
|
||||
json.NewEncoder(w).Encode(response)
|
||||
}
|
||||
|
||||
func (ps *QuizHubCollectorServer) updateQuizHandler(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
|
||||
response := new(client.Response)
|
||||
|
||||
quiz, err := ps.updateQuiz(w, r, params.ByName("id"))
|
||||
if err != nil {
|
||||
response = &client.Response{Status: "error", Content: err.Error()}
|
||||
}
|
||||
|
||||
response = &client.Response{Status: "success", Content: quiz}
|
||||
|
||||
w.WriteHeader(http.StatusAccepted)
|
||||
json.NewEncoder(w).Encode(response)
|
||||
}
|
||||
|
||||
func (ps *QuizHubCollectorServer) readAllQuizzes(w http.ResponseWriter, r *http.Request) *client.Response {
|
||||
|
@ -60,6 +76,27 @@ func (ps *QuizHubCollectorServer) readAllQuizzes(w http.ResponseWriter, r *http.
|
|||
return &client.Response{Status: "success", Content: tests}
|
||||
}
|
||||
|
||||
func (ps *QuizHubCollectorServer) updateQuiz(w http.ResponseWriter, r *http.Request, id string) (*models.Quiz, error) {
|
||||
body, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
updateQuizReq := new(client.UpdateQuizRequest)
|
||||
|
||||
err = json.Unmarshal(body, &updateQuizReq)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
updatedQuiz, err := ps.store.UpdateQuiz(updateQuizReq)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return updatedQuiz, nil
|
||||
}
|
||||
|
||||
func (ps *QuizHubCollectorServer) createQuiz(w http.ResponseWriter, r *http.Request) (*models.Quiz, error) {
|
||||
body, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
|
|
|
@ -38,8 +38,9 @@ func (t *integrationTestSuite) TestQuizCreateAndReadAll() {
|
|||
}
|
||||
`
|
||||
createQuizResponse, err := t.createQuiz(server, payload)
|
||||
t.True(err == nil)
|
||||
t.True(err == nil, "Response should be decoded properly")
|
||||
|
||||
if !t.Failed() {
|
||||
t.Equal("success", createQuizResponse.Status)
|
||||
t.Equal("Question 1", createQuizResponse.Content.Question.Text)
|
||||
t.Equal("Text of the answer 1", createQuizResponse.Content.Answers[0].Text)
|
||||
|
@ -48,12 +49,15 @@ func (t *integrationTestSuite) TestQuizCreateAndReadAll() {
|
|||
t.True(createQuizResponse.Content.ID != "", "Test ID should not be empty")
|
||||
t.True(createQuizResponse.Content.Question.ID != "", "Question ID should not be empty")
|
||||
t.True(createQuizResponse.Content.Answers[0].ID != "", "Answer ID should not be empty")
|
||||
}
|
||||
|
||||
readAllQuizResponse, err := t.readAllQuiz(server)
|
||||
t.True(err == nil)
|
||||
t.True(err == nil, "Response should be decoded properly")
|
||||
|
||||
if !t.Failed() {
|
||||
t.True(len(readAllQuizResponse.Content) == 1, "Length of returned tests should be 1")
|
||||
t.Equal("Question 1", readAllQuizResponse.Content[0].Question.Text)
|
||||
}
|
||||
}
|
||||
|
||||
func (t *integrationTestSuite) TestCatchDuplicateQuiz() {
|
||||
|
|
|
@ -7,6 +7,7 @@ import (
|
|||
"net/http"
|
||||
"net/http/httptest"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"git.andreafazzi.eu/andrea/probo/client"
|
||||
|
@ -76,6 +77,51 @@ func (t *testSuite) TestGETQuestions() {
|
|||
t.Equal(http.StatusOK, response.Code)
|
||||
}
|
||||
|
||||
func (t *testSuite) TestUpdateQuiz() {
|
||||
expectedResponse := &client.UpdateQuizResponse{
|
||||
Status: "success",
|
||||
Content: &models.Quiz{
|
||||
Question: &models.Question{ID: "1", Text: "Question 1"},
|
||||
Answers: []*models.Answer{{}, {}, {}},
|
||||
},
|
||||
}
|
||||
|
||||
store := &StubTestHubCollectorStore{[]*models.Quiz{
|
||||
{
|
||||
Question: &models.Question{ID: "1", Text: "Question 1"},
|
||||
Answers: []*models.Answer{{}, {}, {}},
|
||||
},
|
||||
}}
|
||||
|
||||
server := NewQuizHubCollectorServer(store)
|
||||
|
||||
payload := `
|
||||
{
|
||||
"question": {"text": "Update Question 1"},
|
||||
"answers": [
|
||||
{"text": "Text of the answer 1", "correct": true},
|
||||
{"text": "Text of the answer 2"},
|
||||
{"text": "Text of the answer 3"},
|
||||
{"text": "Text of the answer 4"}
|
||||
]
|
||||
}
|
||||
`
|
||||
request, _ := http.NewRequest(http.MethodPut, "/quizzes/1/update", strings.NewReader(payload))
|
||||
response := httptest.NewRecorder()
|
||||
|
||||
server.ServeHTTP(response, request)
|
||||
|
||||
result, err := decodeUpdateQuizResponse(response.Body)
|
||||
|
||||
t.True(err == nil, fmt.Sprintf("Response should be decoded without errors: %v", err))
|
||||
|
||||
if !t.Failed() {
|
||||
t.True(result.Status == expectedResponse.Status)
|
||||
t.True(reflect.DeepEqual(result, expectedResponse))
|
||||
t.Equal(http.StatusOK, response.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func getResponse(body io.Reader) (response *client.ReadAllQuizResponse) {
|
||||
err := json.NewDecoder(body).Decode(&response)
|
||||
if err != nil {
|
||||
|
@ -85,6 +131,14 @@ func getResponse(body io.Reader) (response *client.ReadAllQuizResponse) {
|
|||
return
|
||||
}
|
||||
|
||||
func decodeUpdateQuizResponse(body io.Reader) (response *client.UpdateQuizResponse, err error) {
|
||||
err = json.NewDecoder(body).Decode(&response)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Unable to parse the response %q from the server: %v", body, err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func testsAreEqual(got, want *client.ReadAllQuizResponse) bool {
|
||||
return reflect.DeepEqual(got, want)
|
||||
}
|
||||
|
|
|
@ -107,7 +107,49 @@ func (s *MemoryQuizHubCollectorStore) ReadAllQuizzes() ([]*models.Quiz, error) {
|
|||
}
|
||||
|
||||
func (s *MemoryQuizHubCollectorStore) CreateQuiz(r *client.CreateQuizRequest) (*models.Quiz, error) {
|
||||
hashes := s.hasher.QuizHashes(r)
|
||||
hashes := s.hasher.QuizHashes(r.Quiz)
|
||||
|
||||
quizID := uuid.New().String()
|
||||
quizHash := hashes[len(hashes)-1]
|
||||
|
||||
quiz := s.readQuiz(quizHash)
|
||||
if quiz != nil { // Quiz is already present in the store
|
||||
return quiz, nil
|
||||
}
|
||||
|
||||
quiz = new(models.Quiz)
|
||||
|
||||
questionHash := hashes[0]
|
||||
q := s.readQuestion(questionHash)
|
||||
if q == nil { // if the question is not in the store then we should add it
|
||||
q = s.createQuestion(questionHash, &models.Question{
|
||||
ID: uuid.New().String(),
|
||||
Text: r.Question.Text,
|
||||
})
|
||||
}
|
||||
|
||||
// Populate Question field
|
||||
quiz.Question = q
|
||||
for i, answer := range r.Answers {
|
||||
answerHash := hashes[i+1]
|
||||
a := s.readAnswer(answerHash)
|
||||
if a == nil { // if the answer is not in the store add it
|
||||
a = s.createAnswer(answerHash, &models.Answer{
|
||||
ID: uuid.New().String(),
|
||||
Text: answer.Text,
|
||||
})
|
||||
if answer.Correct {
|
||||
quiz.Correct = a // s.readAnswer(answerID)
|
||||
}
|
||||
}
|
||||
quiz.Answers = append(quiz.Answers, a)
|
||||
}
|
||||
|
||||
return s.createQuiz(quizID, quizHash, quiz), nil
|
||||
}
|
||||
|
||||
func (s *MemoryQuizHubCollectorStore) UpdateQuiz(r *client.UpdateQuizRequest) (*models.Quiz, error) {
|
||||
hashes := s.hasher.QuizHashes(r.Quiz)
|
||||
|
||||
quizID := uuid.New().String()
|
||||
quizHash := hashes[len(hashes)-1]
|
||||
|
|
|
@ -8,4 +8,5 @@ import (
|
|||
type QuizHubCollectorStore interface {
|
||||
ReadAllQuizzes() ([]*models.Quiz, error)
|
||||
CreateQuiz(r *client.CreateQuizRequest) (*models.Quiz, error)
|
||||
UpdateQuiz(r *client.UpdateQuizRequest) (*models.Quiz, error)
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue