From 9334b293661c3322a6301434ee2e06e7e9533141 Mon Sep 17 00:00:00 2001 From: konrad Date: Sat, 28 Nov 2020 23:08:30 +0000 Subject: [PATCH] Add testing endpoint to reset db tables (#716) Fix lint Better error messages Add docs Add testing endpoint to reset db Co-authored-by: kolaente Reviewed-on: https://kolaente.dev/vikunja/api/pulls/716 Co-Authored-By: konrad Co-Committed-By: konrad --- config.yml.sample | 5 +++ pkg/config/config.go | 1 + pkg/db/dump.go | 25 ++++++++++++- pkg/routes/api/v1/info.go | 3 +- pkg/routes/api/v1/testing.go | 70 ++++++++++++++++++++++++++++++++++++ pkg/routes/routes.go | 5 +++ pkg/swagger/docs.go | 41 +++++++++++++++++++++ pkg/swagger/swagger.json | 41 +++++++++++++++++++++ pkg/swagger/swagger.yaml | 27 ++++++++++++++ 9 files changed, 216 insertions(+), 2 deletions(-) create mode 100644 pkg/routes/api/v1/testing.go diff --git a/config.yml.sample b/config.yml.sample index e13576f2..7d9484d3 100644 --- a/config.yml.sample +++ b/config.yml.sample @@ -34,6 +34,11 @@ service: enabletotp: true # If not empty, enables logging of crashes and unhandled errors in sentry. sentrydsn: '' + # If not empty, this will enable `/test/{table}` endpoints which allow to put any content in the database. + # Used to reset the db before frontend tests. Because this is quite a dangerous feature allowing for lots of harm, + # each request made to this endpoint neefs to provide an `Authorization: ` header with the token from below.
+ # **You should never use this unless you know exactly what you're doing** + testingtoken: '' database: # Database type to use. Supported types are mysql, postgres and sqlite. diff --git a/pkg/config/config.go b/pkg/config/config.go index 6dfd0044..298ec06f 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -51,6 +51,7 @@ const ( ServiceEnableTaskComments Key = `service.enabletaskcomments` ServiceEnableTotp Key = `service.enabletotp` ServiceSentryDsn Key = `service.sentrydsn` + ServiceTestingtoken Key = `service.testingtoken` AuthLocalEnabled Key = `auth.local.enabled` AuthOpenIDEnabled Key = `auth.openid.enabled` diff --git a/pkg/db/dump.go b/pkg/db/dump.go index 70bd3f98..1d304949 100644 --- a/pkg/db/dump.go +++ b/pkg/db/dump.go @@ -16,7 +16,11 @@ package db -import "encoding/json" +import ( + "encoding/json" + + "xorm.io/xorm/schemas" +) // Dump dumps all database tables func Dump() (data map[string][]byte, err error) { @@ -52,3 +56,22 @@ func Restore(table string, contents []map[string]interface{}) (err error) { return } + +// RestoreAndTruncate removes all content from the table before restoring it from the contents map +func RestoreAndTruncate(table string, contents []map[string]interface{}) (err error) { + if _, err := x.IsTableExist(table); err != nil { + return err + } + + if x.Dialect().URI().DBType == schemas.SQLITE { + if _, err := x.Query("DELETE FROM " + table); err != nil { + return err + } + } else { + if _, err := x.Query("TRUNCATE TABLE ?", table); err != nil { + return err + } + } + + return Restore(table, contents) +} diff --git a/pkg/routes/api/v1/info.go b/pkg/routes/api/v1/info.go index 63dd40d4..8b1e97da 100644 --- a/pkg/routes/api/v1/info.go +++ b/pkg/routes/api/v1/info.go @@ -17,9 +17,10 @@ package v1 import ( - "code.vikunja.io/api/pkg/log" "net/http" + "code.vikunja.io/api/pkg/log" + "code.vikunja.io/api/pkg/config" "code.vikunja.io/api/pkg/modules/auth/openid" "code.vikunja.io/api/pkg/modules/migration/todoist" diff --git a/pkg/routes/api/v1/testing.go b/pkg/routes/api/v1/testing.go new file mode 100644 index 00000000..1daee086 --- /dev/null +++ b/pkg/routes/api/v1/testing.go @@ -0,0 +1,70 @@ +// Vikunja is a to-do list application to facilitate your life. +// Copyright 2018-2020 Vikunja and contributors. All rights reserved. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +package v1 + +import ( + "bytes" + "encoding/json" + "net/http" + + "code.vikunja.io/api/pkg/config" + "code.vikunja.io/api/pkg/db" + "github.com/labstack/echo/v4" +) + +// HandleTesting is the web handler to reset the db +// @Summary Reset the db to a defined state +// @Description Fills the specified table with the content provided in the payload. You need to enable the testing endpoint before doing this and provide the `Authorization: ` secret when making requests to this endpoint. See docs for more details. +// @tags testing +// @Accept json +// @Produce json +// @Param table path string true "The table to reset" +// @Success 201 {array} user.User "Everything has been imported successfully." +// @Failure 500 {object} models.Message "Internal server error." +// @Router /test/{table} [patch] +func HandleTesting(c echo.Context) error { + token := c.Request().Header.Get("Authorization") + if token != config.ServiceTestingtoken.GetString() { + return echo.ErrForbidden + } + + table := c.Param("table") + + var buf bytes.Buffer + if _, err := buf.ReadFrom(c.Request().Body); err != nil { + return err + } + + content := []map[string]interface{}{} + err := json.Unmarshal(buf.Bytes(), &content) + if err != nil { + return c.JSON(http.StatusInternalServerError, map[string]interface{}{ + "error": true, + "message": err.Error(), + }) + } + + err = db.RestoreAndTruncate(table, content) + if err != nil { + return c.JSON(http.StatusInternalServerError, map[string]interface{}{ + "error": true, + "message": err.Error(), + }) + } + + return c.JSON(http.StatusCreated, nil) +} diff --git a/pkg/routes/routes.go b/pkg/routes/routes.go index 6de5a256..83cccfcb 100644 --- a/pkg/routes/routes.go +++ b/pkg/routes/routes.go @@ -235,6 +235,11 @@ func registerAPIRoutes(a *echo.Group) { n.POST("/auth/openid/:provider/callback", openid.HandleCallback) } + // Testing + if config.ServiceTestingtoken.GetString() != "" { + n.PATCH("/test/:table", apiv1.HandleTesting) + } + // Info endpoint n.GET("/info", apiv1.Info) diff --git a/pkg/swagger/docs.go b/pkg/swagger/docs.go index 75a15be9..6c6e7723 100644 --- a/pkg/swagger/docs.go +++ b/pkg/swagger/docs.go @@ -5605,6 +5605,47 @@ var doc = `{ } } }, + "/test/{table}": { + "patch": { + "description": "Fills the specified table with the content provided in the payload. You need to enable the testing endpoint before doing this and provide the ` + "`" + `Authorization: \u003ctoken\u003e` + "`" + ` secret when making requests to this endpoint. See docs for more details.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "testing" + ], + "summary": "Reset the db to a defined state", + "parameters": [ + { + "type": "string", + "description": "The table to reset", + "name": "table", + "in": "path", + "required": true + } + ], + "responses": { + "201": { + "description": "Everything has been imported successfully.", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/user.User" + } + } + }, + "500": { + "description": "Internal server error.", + "schema": { + "$ref": "#/definitions/models.Message" + } + } + } + } + }, "/user": { "get": { "security": [ diff --git a/pkg/swagger/swagger.json b/pkg/swagger/swagger.json index ce5b38a2..28280ba8 100644 --- a/pkg/swagger/swagger.json +++ b/pkg/swagger/swagger.json @@ -5588,6 +5588,47 @@ } } }, + "/test/{table}": { + "patch": { + "description": "Fills the specified table with the content provided in the payload. You need to enable the testing endpoint before doing this and provide the `Authorization: \u003ctoken\u003e` secret when making requests to this endpoint. See docs for more details.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "testing" + ], + "summary": "Reset the db to a defined state", + "parameters": [ + { + "type": "string", + "description": "The table to reset", + "name": "table", + "in": "path", + "required": true + } + ], + "responses": { + "201": { + "description": "Everything has been imported successfully.", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/user.User" + } + } + }, + "500": { + "description": "Internal server error.", + "schema": { + "$ref": "#/definitions/models.Message" + } + } + } + } + }, "/user": { "get": { "security": [ diff --git a/pkg/swagger/swagger.yaml b/pkg/swagger/swagger.yaml index 8823fdbc..b69133e6 100644 --- a/pkg/swagger/swagger.yaml +++ b/pkg/swagger/swagger.yaml @@ -4653,6 +4653,33 @@ paths: summary: Toggle a team member's admin status tags: - team + /test/{table}: + patch: + consumes: + - application/json + description: 'Fills the specified table with the content provided in the payload. You need to enable the testing endpoint before doing this and provide the `Authorization: ` secret when making requests to this endpoint. See docs for more details.' + parameters: + - description: The table to reset + in: path + name: table + required: true + type: string + produces: + - application/json + responses: + "201": + description: Everything has been imported successfully. + schema: + items: + $ref: '#/definitions/user.User' + type: array + "500": + description: Internal server error. + schema: + $ref: '#/definitions/models.Message' + summary: Reset the db to a defined state + tags: + - testing /user: get: consumes: