Update quiz

This commit is contained in:
Andrea Fazzi 2022-06-28 13:49:35 +02:00
parent 405113e639
commit ee45c1ba9c
10 changed files with 205 additions and 42 deletions

View file

@ -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
View file

@ -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
View file

@ -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=

View file

@ -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

View file

@ -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)
}

View file

@ -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,34 +23,49 @@ 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:
w.Header().Set("content-type", jsonContentType)
json.NewEncoder(w).Encode(ps.readAllQuizzes(w, r))
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:
response := new(client.Response)
func (ps *QuizHubCollectorServer) createQuizHandler(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
response := new(client.Response)
quiz, err := ps.createQuiz(w, r)
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)
quiz, err := ps.createQuiz(w, r)
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) 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 {

View file

@ -38,22 +38,26 @@ func (t *integrationTestSuite) TestQuizCreateAndReadAll() {
}
`
createQuizResponse, err := t.createQuiz(server, payload)
t.True(err == nil)
t.True(err == nil, "Response should be decoded properly")
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)
t.Equal("Text of the answer 1", createQuizResponse.Content.Correct.Text)
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)
t.Equal("Text of the answer 1", createQuizResponse.Content.Correct.Text)
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")
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")
t.True(len(readAllQuizResponse.Content) == 1, "Length of returned tests should be 1")
t.Equal("Question 1", readAllQuizResponse.Content[0].Question.Text)
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() {

View file

@ -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)
}

View file

@ -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]

View file

@ -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)
}