Commit 2429be4c authored by Julien Schröter's avatar Julien Schröter

Add report endpoint

parent 50f4703a
Pipeline #2013 passed with stages
in 20 minutes and 41 seconds
...@@ -648,6 +648,34 @@ paths: ...@@ -648,6 +648,34 @@ paths:
description: User is denied to answer that roundquestion, either because that roundquestion has already been answered by that user or the user is not participant of the related duel. description: User is denied to answer that roundquestion, either because that roundquestion has already been answered by that user or the user is not participant of the related duel.
'404': '404':
description: A roundquestion with that id does not exist. description: A roundquestion with that id does not exist.
'/duel/report':
post:
summary: Report a mistake in a question.
description: Endpoint to report a mistake in a question, its answers or its explaination.
parameters:
- name: question
description: ID of the falty question.
in: query
required: true
schema:
type: integer
format: uint32
requestBody:
description: Object with the contents of the report.
content:
application/json:
schema:
type: object
required:
- text
properties:
text:
type: string
responses:
'201':
description: Report has been submitted.
'404':
description: There is no question with the given id.
'/friend': '/friend':
get: get:
summary: "Get a list of your friends." summary: "Get a list of your friends."
......
...@@ -45,6 +45,8 @@ func SetupDuelRoutes(group *gin.RouterGroup, repository DuelQuery, titleReposito ...@@ -45,6 +45,8 @@ func SetupDuelRoutes(group *gin.RouterGroup, repository DuelQuery, titleReposito
//Endpoint to answer a questino of a round //Endpoint to answer a questino of a round
group.POST("/answer", postAnswer(repository, titleRepository, sessionRepository)) group.POST("/answer", postAnswer(repository, titleRepository, sessionRepository))
group.POST("/report", postReport(repository))
} }
func getDuel(repository DuelQuery) gin.HandlerFunc { func getDuel(repository DuelQuery) gin.HandlerFunc {
...@@ -410,6 +412,36 @@ func postAnswer(repository DuelQuery, titleRepository title.TitleQuery, sessionR ...@@ -410,6 +412,36 @@ func postAnswer(repository DuelQuery, titleRepository title.TitleQuery, sessionR
} }
} }
func postReport(repository DuelQuery) gin.HandlerFunc {
return func(ctx *gin.Context) {
var req struct {
QuestionID uint32 `form:"question" binding:"required_without=Text"`
Text string `json:"text" binding:"required_without=QuestionID"`
}
if err := ctx.BindQuery(&req); err != nil {
ctx.AbortWithError(http.StatusBadRequest, err)
return
}
if err := ctx.BindJSON(&req); err != nil {
ctx.AbortWithError(http.StatusBadRequest, err)
return
}
if err := repository.InsertReport(req.QuestionID, req.Text); err == ErrQuestionNotFound {
ctx.Status(http.StatusNotFound)
return
} else if err != nil {
raven.CaptureError(err, nil)
ctx.AbortWithError(http.StatusInternalServerError, err)
return
}
ctx.Status(http.StatusCreated)
}
}
func notifyUser(nt notifications.NotificationType, sessionRepository refreshjwt.RefreshJWTQuery, userID uint32, message *messaging.Notification, data map[string]string) error { func notifyUser(nt notifications.NotificationType, sessionRepository refreshjwt.RefreshJWTQuery, userID uint32, message *messaging.Notification, data map[string]string) error {
// Fetch user token // Fetch user token
token, err := sessionRepository.GetLatestToken(userID) token, err := sessionRepository.GetLatestToken(userID)
......
...@@ -226,6 +226,9 @@ var ErrInvalidAnswer = errors.New("given answer was not of expected form") ...@@ -226,6 +226,9 @@ var ErrInvalidAnswer = errors.New("given answer was not of expected form")
// ErrRoundQuestionNotFound is returned if no roundquestion was returned by a parameterized query // ErrRoundQuestionNotFound is returned if no roundquestion was returned by a parameterized query
var ErrRoundQuestionNotFound = errors.New("roundquestion not found") var ErrRoundQuestionNotFound = errors.New("roundquestion not found")
// ErrQuestionNotFound is returned if a referenced question does not exist.
var ErrQuestionNotFound = errors.New("question not found")
var ErrBadRequest = errors.New("The request was either formatted incorrectly or it was not allowed at the currect state of the database.") var ErrBadRequest = errors.New("The request was either formatted incorrectly or it was not allowed at the currect state of the database.")
const TotalRounds = 2 const TotalRounds = 2
...@@ -274,6 +277,7 @@ type DuelQuery interface { ...@@ -274,6 +277,7 @@ type DuelQuery interface {
InsertTextUserAnswer(roundID, questionID, userID uint32, answer string) (*UserAwards, error) InsertTextUserAnswer(roundID, questionID, userID uint32, answer string) (*UserAwards, error)
IsPlayerOfRound(userID, roundID uint32) (bool, error) IsPlayerOfRound(userID, roundID uint32) (bool, error)
CanAnswerQuestion(userID, roundID, questionID uint32) (bool, error) CanAnswerQuestion(userID, roundID, questionID uint32) (bool, error)
InsertReport(questionID uint32, text string) error
} }
// MySQLDuelQuery provides functionality to create, get and update duels in a MySQL database. // MySQLDuelQuery provides functionality to create, get and update duels in a MySQL database.
...@@ -1466,3 +1470,24 @@ func (*MySQLDuelQuery) CanAnswerQuestion(userID, roundID, questionID uint32) (bo ...@@ -1466,3 +1470,24 @@ func (*MySQLDuelQuery) CanAnswerQuestion(userID, roundID, questionID uint32) (bo
return count == 0, nil return count == 0, nil
} }
// InsertReport creates a report with the given questionID and text. ErrQuestionNotFound will
// be returned when the referenced questionID does not belong to any question.
func (*MySQLDuelQuery) InsertReport(questionID uint32, text string) error {
db, err := dbhandler.GetDBConnection()
if err != nil {
return err
}
_, err = db.Exec("INSERT INTO `report` (`question`, `test`) VALUES (?, ?)", questionID, text)
if err != nil {
if merr := err.(*mysql.MySQLError); merr.Number == 1452 {
// Question does not exist
return ErrQuestionNotFound
}
return err
}
return nil
}
...@@ -12,6 +12,7 @@ TRUNCATE TABLE `maintainer`; ...@@ -12,6 +12,7 @@ TRUNCATE TABLE `maintainer`;
TRUNCATE TABLE `user`; TRUNCATE TABLE `user`;
TRUNCATE TABLE `user_title`; TRUNCATE TABLE `user_title`;
TRUNCATE TABLE `question`; TRUNCATE TABLE `question`;
TRUNCATE TABLE `report`;
TRUNCATE TABLE `duel_player`; TRUNCATE TABLE `duel_player`;
TRUNCATE TABLE `duel`; TRUNCATE TABLE `duel`;
TRUNCATE TABLE `roundquestion`; TRUNCATE TABLE `roundquestion`;
......
...@@ -162,6 +162,9 @@ func (m *mockDuel) IsPlayerOfRound(userID, roundID uint32) (bool, error) { ...@@ -162,6 +162,9 @@ func (m *mockDuel) IsPlayerOfRound(userID, roundID uint32) (bool, error) {
func (m *mockDuel) CanAnswerQuestion(userID, roundID, questionID uint32) (bool, error) { func (m *mockDuel) CanAnswerQuestion(userID, roundID, questionID uint32) (bool, error) {
return false, errors.New("query not mocked") return false, errors.New("query not mocked")
} }
func (m *mockDuel) InsertReport(questionID uint32, text string) error {
return errors.New("query not mocked")
}
func newMockDuel() mockDuel { func newMockDuel() mockDuel {
var mock mockDuel var mock mockDuel
......
...@@ -352,6 +352,69 @@ func getDuelPool403(router *gin.Engine) func(t *testing.T) { ...@@ -352,6 +352,69 @@ func getDuelPool403(router *gin.Engine) func(t *testing.T) {
} }
} }
func postReport201(router *gin.Engine) func(t *testing.T) {
return func(t *testing.T) {
env := new(test.TestEnv)
env.Router = router
env.Login("user1", "Password1", t)
res := httptest.NewRecorder()
req, _ := env.NewParameterRequest("POST", "/duel/report", map[string]string{"question": "3"}, []byte("{\"text\":\"my report\"}"))
router.ServeHTTP(res, req)
assert.Equal(t, 201, res.Code)
db, err := dbhandler.GetDBConnection()
if err != nil {
t.Skip("skipping postReport201 since unable to fetch database instance")
}
row := db.QueryRow("SELECT `question`, `test` FROM `report`")
var questionID uint32
var text string
if err := row.Scan(&questionID, &text); err != nil {
t.Error("failed to query database result")
}
assert.EqualValues(t, 3, questionID)
assert.Equal(t, "my report", text)
}
}
func postReport404(router *gin.Engine) func(t *testing.T) {
return func(t *testing.T) {
env := new(test.TestEnv)
env.Router = router
env.Login("user1", "Password1", t)
res := httptest.NewRecorder()
req, _ := env.NewParameterRequest("POST", "/duel/report", map[string]string{"question": "123"}, []byte("{\"text\":\"no report\"}"))
router.ServeHTTP(res, req)
assert.Equal(t, 404, res.Code)
db, err := dbhandler.GetDBConnection()
if err != nil {
t.Skip("skipping postReport201 since unable to fetch database instance")
}
row := db.QueryRow("SELECT COUNT(`idreport`), `question`, `test` FROM `report`")
var count, questionID uint32
var text string
if err := row.Scan(&count, &questionID, &text); err != nil {
t.Error("failed to query database result")
}
assert.EqualValues(t, 1, count)
assert.NotEqual(t, uint32(123), questionID)
assert.NotEqual(t, "no report", text)
}
}
func Test_getDuelByID(t *testing.T) { func Test_getDuelByID(t *testing.T) {
env := E2EEnvironment{} env := E2EEnvironment{}
...@@ -437,3 +500,16 @@ func Test_setPool(t *testing.T) { ...@@ -437,3 +500,16 @@ func Test_setPool(t *testing.T) {
t.Run("setPool200", testPatchPool200(router)) t.Run("setPool200", testPatchPool200(router))
} }
func Test_postReport(t *testing.T) {
env := E2EEnvironment{}
router, setuperr := env.SetupEnvironment()
if setuperr != nil {
t.Fail()
t.Skip("Skipping E2E getDuel tests. Environment setup failed.", setuperr.Error())
}
t.Run("postReport201", postReport201(router))
t.Run("postReport404", postReport404(router))
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment