mirror of
https://github.com/hanzoai/base.git
synced 2026-10-21 03:41:50 +00:00
Three things, all the same shape: SQLite's guarantees were being assumed on an engine that does not make them. A backup archives the data directory, which is where the database lives on SQLite and not on Postgres. There it returned 204 and produced an archive with no database in it, and restore then failed inside a goroutine after the 204 had gone out — so an operator saw successful backups, a populated list, plausible sizes, and had nothing recoverable. It refuses on an engine whose database is not in that directory, before extracting anything, and says where to take the backup instead. Refusing beats shelling out to pg_dump: that adds a binary that has to out-version the server, and its output cannot be restored by the path we have, so it would be a second restore mechanism. What stopped a restore is now retrievable. It is recorded in core, so a CLI or a hook caller sees it too, and GET on the restore address renders it: 202 while running, 200 and the reason when stopped, 404 when this process has not tried. POST still answers 204, because the admin client reads a body on any other 2xx. A serialization failure or a deadlock is a transient and is retried at the transaction boundary, which is the only place a retry means anything — the second statement of a deadlocked transaction reports 25P02, so retrying a statement spends the attempt and loses the evidence. The transaction watches its own statements for the two SQLSTATEs rather than reading the returned error, which by then has been wrapped down to a message string. Batch had to become re-runnable for this: it accumulated results outside the transaction. Retrying turns a transient into a success and does not pretend to fix a convoy: with a few concurrent opposing batches the loss goes to zero, and with thirty pairs over six rows every pair collides and no budget clears it. SQLite keeps the same executions at the same intervals. Co-authored-by: Hanzo Dev <[email protected]>
901 lines
30 KiB
Go
901 lines
30 KiB
Go
package apis_test
|
|
|
|
import (
|
|
"archive/zip"
|
|
"bytes"
|
|
"context"
|
|
"io"
|
|
"mime/multipart"
|
|
"net/http"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/hanzoai/base/apis"
|
|
"github.com/hanzoai/base/core"
|
|
"github.com/hanzoai/base/tests"
|
|
"github.com/hanzoai/base/tools/filesystem/blob"
|
|
"github.com/hanzoai/base/tools/types"
|
|
)
|
|
|
|
func TestBackupsList(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
scenarios := []tests.ApiScenario{
|
|
{
|
|
Name: "unauthorized",
|
|
Method: http.MethodGet,
|
|
URL: "/v1/backups",
|
|
BeforeTestFunc: func(t testing.TB, app *tests.TestApp, e *core.ServeEvent) {
|
|
if err := createTestBackups(app); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
},
|
|
ExpectedStatus: 401,
|
|
ExpectedContent: []string{`"data":{}`},
|
|
ExpectedEvents: map[string]int{"*": 0},
|
|
},
|
|
{
|
|
Name: "authorized as regular user",
|
|
Method: http.MethodGet,
|
|
URL: "/v1/backups",
|
|
Headers: map[string]string{
|
|
"Authorization": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjb2xsZWN0aW9uSWQiOiJfdXNlcnNfYXV0aF8iLCJleHAiOjI1MjQ2MDQ0NjEsImlkIjoiNHExeGxjbG1mbG9rdTMzIiwicmVmcmVzaGFibGUiOnRydWUsInR5cGUiOiJhdXRoIn0.AuFTIzCsdLEy-5adFzpjZzbqAdTP6Iu9B1wPBAxLBgo",
|
|
},
|
|
BeforeTestFunc: func(t testing.TB, app *tests.TestApp, e *core.ServeEvent) {
|
|
if err := createTestBackups(app); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
},
|
|
ExpectedStatus: 403,
|
|
ExpectedContent: []string{`"data":{}`},
|
|
ExpectedEvents: map[string]int{"*": 0},
|
|
},
|
|
{
|
|
Name: "authorized as superuser (empty list)",
|
|
Method: http.MethodGet,
|
|
URL: "/v1/backups",
|
|
Headers: map[string]string{
|
|
"Authorization": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjb2xsZWN0aW9uSWQiOiJoYmNfMzE0MjYzNTgyMyIsImV4cCI6MjUyNDYwNDQ2MSwiaWQiOiJzeXdiaGVjbmg0NnJobTAiLCJyZWZyZXNoYWJsZSI6dHJ1ZSwidHlwZSI6ImF1dGgifQ.CXBf8BazmUeg2RnJW8OEs1UFYF41rbCMOa6YZa4wZio",
|
|
},
|
|
ExpectedStatus: 200,
|
|
ExpectedContent: []string{`[]`},
|
|
ExpectedEvents: map[string]int{"*": 0},
|
|
},
|
|
{
|
|
Name: "authorized as superuser",
|
|
Method: http.MethodGet,
|
|
URL: "/v1/backups",
|
|
Headers: map[string]string{
|
|
"Authorization": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjb2xsZWN0aW9uSWQiOiJoYmNfMzE0MjYzNTgyMyIsImV4cCI6MjUyNDYwNDQ2MSwiaWQiOiJzeXdiaGVjbmg0NnJobTAiLCJyZWZyZXNoYWJsZSI6dHJ1ZSwidHlwZSI6ImF1dGgifQ.CXBf8BazmUeg2RnJW8OEs1UFYF41rbCMOa6YZa4wZio",
|
|
},
|
|
BeforeTestFunc: func(t testing.TB, app *tests.TestApp, e *core.ServeEvent) {
|
|
if err := createTestBackups(app); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
},
|
|
ExpectedStatus: 200,
|
|
ExpectedContent: []string{
|
|
`"test1.zip"`,
|
|
`"test2.zip"`,
|
|
`"test3.zip"`,
|
|
},
|
|
ExpectedEvents: map[string]int{"*": 0},
|
|
},
|
|
}
|
|
|
|
for _, scenario := range scenarios {
|
|
scenario.Test(t)
|
|
}
|
|
}
|
|
|
|
func TestBackupsCreate(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
scenarios := []tests.ApiScenario{
|
|
{
|
|
Name: "unauthorized",
|
|
Method: http.MethodPost,
|
|
URL: "/v1/backups",
|
|
AfterTestFunc: func(t testing.TB, app *tests.TestApp, res *http.Response) {
|
|
ensureNoBackups(t, app)
|
|
},
|
|
ExpectedStatus: 401,
|
|
ExpectedContent: []string{`"data":{}`},
|
|
ExpectedEvents: map[string]int{"*": 0},
|
|
},
|
|
{
|
|
Name: "authorized as regular user",
|
|
Method: http.MethodPost,
|
|
URL: "/v1/backups",
|
|
Headers: map[string]string{
|
|
"Authorization": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjb2xsZWN0aW9uSWQiOiJfdXNlcnNfYXV0aF8iLCJleHAiOjI1MjQ2MDQ0NjEsImlkIjoiNHExeGxjbG1mbG9rdTMzIiwicmVmcmVzaGFibGUiOnRydWUsInR5cGUiOiJhdXRoIn0.AuFTIzCsdLEy-5adFzpjZzbqAdTP6Iu9B1wPBAxLBgo",
|
|
},
|
|
AfterTestFunc: func(t testing.TB, app *tests.TestApp, res *http.Response) {
|
|
ensureNoBackups(t, app)
|
|
},
|
|
ExpectedStatus: 403,
|
|
ExpectedContent: []string{`"data":{}`},
|
|
ExpectedEvents: map[string]int{"*": 0},
|
|
},
|
|
{
|
|
Name: "authorized as superuser (pending backup)",
|
|
Method: http.MethodPost,
|
|
URL: "/v1/backups",
|
|
Headers: map[string]string{
|
|
"Authorization": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjb2xsZWN0aW9uSWQiOiJoYmNfMzE0MjYzNTgyMyIsImV4cCI6MjUyNDYwNDQ2MSwiaWQiOiJzeXdiaGVjbmg0NnJobTAiLCJyZWZyZXNoYWJsZSI6dHJ1ZSwidHlwZSI6ImF1dGgifQ.CXBf8BazmUeg2RnJW8OEs1UFYF41rbCMOa6YZa4wZio",
|
|
},
|
|
BeforeTestFunc: func(t testing.TB, app *tests.TestApp, e *core.ServeEvent) {
|
|
app.Store().Set(core.StoreKeyActiveBackup, "")
|
|
},
|
|
AfterTestFunc: func(t testing.TB, app *tests.TestApp, res *http.Response) {
|
|
ensureNoBackups(t, app)
|
|
},
|
|
ExpectedStatus: 400,
|
|
ExpectedContent: []string{`"data":{}`},
|
|
ExpectedEvents: map[string]int{"*": 0},
|
|
},
|
|
{
|
|
Name: "authorized as superuser (autogenerated name)",
|
|
Method: http.MethodPost,
|
|
URL: "/v1/backups",
|
|
Headers: map[string]string{
|
|
"Authorization": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjb2xsZWN0aW9uSWQiOiJoYmNfMzE0MjYzNTgyMyIsImV4cCI6MjUyNDYwNDQ2MSwiaWQiOiJzeXdiaGVjbmg0NnJobTAiLCJyZWZyZXNoYWJsZSI6dHJ1ZSwidHlwZSI6ImF1dGgifQ.CXBf8BazmUeg2RnJW8OEs1UFYF41rbCMOa6YZa4wZio",
|
|
},
|
|
AfterTestFunc: func(t testing.TB, app *tests.TestApp, res *http.Response) {
|
|
files, err := getBackupFiles(app)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if total := len(files); total != 1 {
|
|
t.Fatalf("Expected 1 backup file, got %d", total)
|
|
}
|
|
|
|
expected := "hz_backup_"
|
|
if !strings.HasPrefix(files[0].Key, expected) {
|
|
t.Fatalf("Expected backup file with prefix %q, got %q", expected, files[0].Key)
|
|
}
|
|
},
|
|
ExpectedStatus: 204,
|
|
ExpectedEvents: map[string]int{
|
|
"*": 0,
|
|
"OnBackupCreate": 1,
|
|
},
|
|
},
|
|
{
|
|
Name: "authorized as superuser (invalid name)",
|
|
Method: http.MethodPost,
|
|
URL: "/v1/backups",
|
|
Body: strings.NewReader(`{"name":"!test.zip"}`),
|
|
Headers: map[string]string{
|
|
"Authorization": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjb2xsZWN0aW9uSWQiOiJoYmNfMzE0MjYzNTgyMyIsImV4cCI6MjUyNDYwNDQ2MSwiaWQiOiJzeXdiaGVjbmg0NnJobTAiLCJyZWZyZXNoYWJsZSI6dHJ1ZSwidHlwZSI6ImF1dGgifQ.CXBf8BazmUeg2RnJW8OEs1UFYF41rbCMOa6YZa4wZio",
|
|
},
|
|
AfterTestFunc: func(t testing.TB, app *tests.TestApp, res *http.Response) {
|
|
ensureNoBackups(t, app)
|
|
},
|
|
ExpectedStatus: 400,
|
|
ExpectedContent: []string{
|
|
`"data":{`,
|
|
`"name":{"code":"validation_match_invalid"`,
|
|
},
|
|
ExpectedEvents: map[string]int{"*": 0},
|
|
},
|
|
{
|
|
Name: "authorized as superuser (valid name)",
|
|
Method: http.MethodPost,
|
|
URL: "/v1/backups",
|
|
Body: strings.NewReader(`{"name":"test.zip"}`),
|
|
Headers: map[string]string{
|
|
"Authorization": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjb2xsZWN0aW9uSWQiOiJoYmNfMzE0MjYzNTgyMyIsImV4cCI6MjUyNDYwNDQ2MSwiaWQiOiJzeXdiaGVjbmg0NnJobTAiLCJyZWZyZXNoYWJsZSI6dHJ1ZSwidHlwZSI6ImF1dGgifQ.CXBf8BazmUeg2RnJW8OEs1UFYF41rbCMOa6YZa4wZio",
|
|
},
|
|
AfterTestFunc: func(t testing.TB, app *tests.TestApp, res *http.Response) {
|
|
files, err := getBackupFiles(app)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if total := len(files); total != 1 {
|
|
t.Fatalf("Expected 1 backup file, got %d", total)
|
|
}
|
|
|
|
expected := "test.zip"
|
|
if files[0].Key != expected {
|
|
t.Fatalf("Expected backup file %q, got %q", expected, files[0].Key)
|
|
}
|
|
},
|
|
ExpectedStatus: 204,
|
|
ExpectedEvents: map[string]int{
|
|
"*": 0,
|
|
"OnBackupCreate": 1,
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, scenario := range scenarios {
|
|
scenario.Test(t)
|
|
}
|
|
}
|
|
|
|
func TestBackupUpload(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
// create dummy form data bodies
|
|
type body struct {
|
|
buffer io.Reader
|
|
contentType string
|
|
}
|
|
bodies := make([]body, 10)
|
|
for i := 0; i < 10; i++ {
|
|
func() {
|
|
zb := new(bytes.Buffer)
|
|
zw := zip.NewWriter(zb)
|
|
if err := zw.Close(); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
b := new(bytes.Buffer)
|
|
mw := multipart.NewWriter(b)
|
|
|
|
mfw, err := mw.CreateFormFile("file", "test")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := io.Copy(mfw, zb); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
mw.Close()
|
|
|
|
bodies[i] = body{
|
|
buffer: b,
|
|
contentType: mw.FormDataContentType(),
|
|
}
|
|
}()
|
|
}
|
|
// ---
|
|
|
|
scenarios := []tests.ApiScenario{
|
|
{
|
|
Name: "unauthorized",
|
|
Method: http.MethodPost,
|
|
URL: "/v1/backups/upload",
|
|
Body: bodies[0].buffer,
|
|
Headers: map[string]string{
|
|
"Content-Type": bodies[0].contentType,
|
|
},
|
|
AfterTestFunc: func(t testing.TB, app *tests.TestApp, res *http.Response) {
|
|
ensureNoBackups(t, app)
|
|
},
|
|
ExpectedStatus: 401,
|
|
ExpectedContent: []string{`"data":{}`},
|
|
ExpectedEvents: map[string]int{"*": 0},
|
|
},
|
|
{
|
|
Name: "authorized as regular user",
|
|
Method: http.MethodPost,
|
|
URL: "/v1/backups/upload",
|
|
Body: bodies[1].buffer,
|
|
Headers: map[string]string{
|
|
"Content-Type": bodies[1].contentType,
|
|
"Authorization": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjb2xsZWN0aW9uSWQiOiJfdXNlcnNfYXV0aF8iLCJleHAiOjI1MjQ2MDQ0NjEsImlkIjoiNHExeGxjbG1mbG9rdTMzIiwicmVmcmVzaGFibGUiOnRydWUsInR5cGUiOiJhdXRoIn0.AuFTIzCsdLEy-5adFzpjZzbqAdTP6Iu9B1wPBAxLBgo",
|
|
},
|
|
AfterTestFunc: func(t testing.TB, app *tests.TestApp, res *http.Response) {
|
|
ensureNoBackups(t, app)
|
|
},
|
|
ExpectedStatus: 403,
|
|
ExpectedContent: []string{`"data":{}`},
|
|
ExpectedEvents: map[string]int{"*": 0},
|
|
},
|
|
{
|
|
Name: "authorized as superuser (missing file)",
|
|
Method: http.MethodPost,
|
|
URL: "/v1/backups/upload",
|
|
Headers: map[string]string{
|
|
"Authorization": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjb2xsZWN0aW9uSWQiOiJoYmNfMzE0MjYzNTgyMyIsImV4cCI6MjUyNDYwNDQ2MSwiaWQiOiJzeXdiaGVjbmg0NnJobTAiLCJyZWZyZXNoYWJsZSI6dHJ1ZSwidHlwZSI6ImF1dGgifQ.CXBf8BazmUeg2RnJW8OEs1UFYF41rbCMOa6YZa4wZio",
|
|
},
|
|
AfterTestFunc: func(t testing.TB, app *tests.TestApp, res *http.Response) {
|
|
ensureNoBackups(t, app)
|
|
},
|
|
ExpectedStatus: 400,
|
|
ExpectedContent: []string{`"data":{`},
|
|
ExpectedEvents: map[string]int{"*": 0},
|
|
},
|
|
{
|
|
Name: "authorized as superuser (existing backup name)",
|
|
Method: http.MethodPost,
|
|
URL: "/v1/backups/upload",
|
|
Body: bodies[3].buffer,
|
|
Headers: map[string]string{
|
|
"Content-Type": bodies[3].contentType,
|
|
"Authorization": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjb2xsZWN0aW9uSWQiOiJoYmNfMzE0MjYzNTgyMyIsImV4cCI6MjUyNDYwNDQ2MSwiaWQiOiJzeXdiaGVjbmg0NnJobTAiLCJyZWZyZXNoYWJsZSI6dHJ1ZSwidHlwZSI6ImF1dGgifQ.CXBf8BazmUeg2RnJW8OEs1UFYF41rbCMOa6YZa4wZio",
|
|
},
|
|
BeforeTestFunc: func(t testing.TB, app *tests.TestApp, e *core.ServeEvent) {
|
|
fsys, err := app.NewBackupsFilesystem()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer fsys.Close()
|
|
// create a dummy backup file to simulate existing backups
|
|
if err := fsys.Upload([]byte("123"), "test"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
},
|
|
AfterTestFunc: func(t testing.TB, app *tests.TestApp, res *http.Response) {
|
|
files, _ := getBackupFiles(app)
|
|
if total := len(files); total != 1 {
|
|
t.Fatalf("Expected %d backup file, got %d", 1, total)
|
|
}
|
|
},
|
|
ExpectedStatus: 400,
|
|
ExpectedContent: []string{`"data":{"file":{`},
|
|
ExpectedEvents: map[string]int{"*": 0},
|
|
},
|
|
{
|
|
Name: "authorized as superuser (valid file)",
|
|
Method: http.MethodPost,
|
|
URL: "/v1/backups/upload",
|
|
Body: bodies[4].buffer,
|
|
Headers: map[string]string{
|
|
"Content-Type": bodies[4].contentType,
|
|
"Authorization": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjb2xsZWN0aW9uSWQiOiJoYmNfMzE0MjYzNTgyMyIsImV4cCI6MjUyNDYwNDQ2MSwiaWQiOiJzeXdiaGVjbmg0NnJobTAiLCJyZWZyZXNoYWJsZSI6dHJ1ZSwidHlwZSI6ImF1dGgifQ.CXBf8BazmUeg2RnJW8OEs1UFYF41rbCMOa6YZa4wZio",
|
|
},
|
|
AfterTestFunc: func(t testing.TB, app *tests.TestApp, res *http.Response) {
|
|
files, _ := getBackupFiles(app)
|
|
if total := len(files); total != 1 {
|
|
t.Fatalf("Expected %d backup file, got %d", 1, total)
|
|
}
|
|
},
|
|
ExpectedStatus: 204,
|
|
ExpectedEvents: map[string]int{"*": 0},
|
|
},
|
|
{
|
|
Name: "ensure that the default body limit is skipped",
|
|
Method: http.MethodPost,
|
|
URL: "/v1/backups/upload",
|
|
Body: bytes.NewBuffer(make([]byte, apis.DefaultMaxBodySize+100)),
|
|
Headers: map[string]string{
|
|
"Authorization": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjb2xsZWN0aW9uSWQiOiJoYmNfMzE0MjYzNTgyMyIsImV4cCI6MjUyNDYwNDQ2MSwiaWQiOiJzeXdiaGVjbmg0NnJobTAiLCJyZWZyZXNoYWJsZSI6dHJ1ZSwidHlwZSI6ImF1dGgifQ.CXBf8BazmUeg2RnJW8OEs1UFYF41rbCMOa6YZa4wZio",
|
|
},
|
|
ExpectedStatus: 400, // it doesn't matter as long as it is not 413
|
|
ExpectedContent: []string{`"data":{`},
|
|
NotExpectedContent: []string{"entity too large"},
|
|
ExpectedEvents: map[string]int{"*": 0},
|
|
},
|
|
}
|
|
|
|
for _, scenario := range scenarios {
|
|
scenario.Test(t)
|
|
}
|
|
}
|
|
|
|
func TestBackupsDownload(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
scenarios := []tests.ApiScenario{
|
|
{
|
|
Name: "unauthorized",
|
|
Method: http.MethodGet,
|
|
URL: "/v1/backups/test1.zip",
|
|
BeforeTestFunc: func(t testing.TB, app *tests.TestApp, e *core.ServeEvent) {
|
|
if err := createTestBackups(app); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
},
|
|
ExpectedStatus: 403,
|
|
ExpectedContent: []string{`"data":{}`},
|
|
ExpectedEvents: map[string]int{"*": 0},
|
|
},
|
|
{
|
|
Name: "with record auth header",
|
|
Method: http.MethodGet,
|
|
URL: "/v1/backups/test1.zip",
|
|
Headers: map[string]string{
|
|
"Authorization": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjb2xsZWN0aW9uSWQiOiJfdXNlcnNfYXV0aF8iLCJleHAiOjI1MjQ2MDQ0NjEsImlkIjoiNHExeGxjbG1mbG9rdTMzIiwicmVmcmVzaGFibGUiOnRydWUsInR5cGUiOiJhdXRoIn0.AuFTIzCsdLEy-5adFzpjZzbqAdTP6Iu9B1wPBAxLBgo",
|
|
},
|
|
BeforeTestFunc: func(t testing.TB, app *tests.TestApp, e *core.ServeEvent) {
|
|
if err := createTestBackups(app); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
},
|
|
ExpectedStatus: 403,
|
|
ExpectedContent: []string{`"data":{}`},
|
|
ExpectedEvents: map[string]int{"*": 0},
|
|
},
|
|
{
|
|
Name: "with superuser auth header",
|
|
Method: http.MethodGet,
|
|
URL: "/v1/backups/test1.zip",
|
|
Headers: map[string]string{
|
|
"Authorization": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjb2xsZWN0aW9uSWQiOiJoYmNfMzE0MjYzNTgyMyIsImV4cCI6MjUyNDYwNDQ2MSwiaWQiOiJzeXdiaGVjbmg0NnJobTAiLCJyZWZyZXNoYWJsZSI6dHJ1ZSwidHlwZSI6ImF1dGgifQ.CXBf8BazmUeg2RnJW8OEs1UFYF41rbCMOa6YZa4wZio",
|
|
},
|
|
BeforeTestFunc: func(t testing.TB, app *tests.TestApp, e *core.ServeEvent) {
|
|
if err := createTestBackups(app); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
},
|
|
ExpectedStatus: 403,
|
|
ExpectedContent: []string{`"data":{}`},
|
|
ExpectedEvents: map[string]int{"*": 0},
|
|
},
|
|
{
|
|
Name: "with empty or invalid token",
|
|
Method: http.MethodGet,
|
|
URL: "/v1/backups/test1.zip?token=",
|
|
BeforeTestFunc: func(t testing.TB, app *tests.TestApp, e *core.ServeEvent) {
|
|
if err := createTestBackups(app); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
},
|
|
ExpectedStatus: 403,
|
|
ExpectedContent: []string{`"data":{}`},
|
|
ExpectedEvents: map[string]int{"*": 0},
|
|
},
|
|
{
|
|
Name: "with valid record auth token",
|
|
Method: http.MethodGet,
|
|
URL: "/v1/backups/test1.zip?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjb2xsZWN0aW9uSWQiOiJfdXNlcnNfYXV0aF8iLCJleHAiOjI1MjQ2MDQ0NjEsImlkIjoiNHExeGxjbG1mbG9rdTMzIiwicmVmcmVzaGFibGUiOnRydWUsInR5cGUiOiJhdXRoIn0.AuFTIzCsdLEy-5adFzpjZzbqAdTP6Iu9B1wPBAxLBgo",
|
|
BeforeTestFunc: func(t testing.TB, app *tests.TestApp, e *core.ServeEvent) {
|
|
if err := createTestBackups(app); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
},
|
|
ExpectedStatus: 403,
|
|
ExpectedContent: []string{`"data":{}`},
|
|
ExpectedEvents: map[string]int{"*": 0},
|
|
},
|
|
{
|
|
Name: "with valid record file token",
|
|
Method: http.MethodGet,
|
|
URL: "/v1/backups/test1.zip?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjb2xsZWN0aW9uSWQiOiJfdXNlcnNfYXV0aF8iLCJleHAiOjQ5MjI0MTcyMTAsImlkIjoiNHExeGxjbG1mbG9rdTMzIiwidHlwZSI6ImZpbGUifQ.eE47sRcF1Vm4DQ7bpRFntr3Xr2wjPVlwJtdgssP9Rxw",
|
|
BeforeTestFunc: func(t testing.TB, app *tests.TestApp, e *core.ServeEvent) {
|
|
if err := createTestBackups(app); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
},
|
|
ExpectedStatus: 403,
|
|
ExpectedContent: []string{`"data":{}`},
|
|
ExpectedEvents: map[string]int{"*": 0},
|
|
},
|
|
{
|
|
Name: "with valid superuser auth token",
|
|
Method: http.MethodGet,
|
|
URL: "/v1/backups/test1.zip?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjb2xsZWN0aW9uSWQiOiJoYmNfMzE0MjYzNTgyMyIsImV4cCI6MjUyNDYwNDQ2MSwiaWQiOiJzeXdiaGVjbmg0NnJobTAiLCJyZWZyZXNoYWJsZSI6dHJ1ZSwidHlwZSI6ImF1dGgifQ.CXBf8BazmUeg2RnJW8OEs1UFYF41rbCMOa6YZa4wZio",
|
|
BeforeTestFunc: func(t testing.TB, app *tests.TestApp, e *core.ServeEvent) {
|
|
if err := createTestBackups(app); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
},
|
|
ExpectedStatus: 403,
|
|
ExpectedContent: []string{`"data":{}`},
|
|
ExpectedEvents: map[string]int{"*": 0},
|
|
},
|
|
{
|
|
Name: "with expired superuser file token",
|
|
Method: http.MethodGet,
|
|
URL: "/v1/backups/test1.zip?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjb2xsZWN0aW9uSWQiOiJoYmNfMzE0MjYzNTgyMyIsImV4cCI6MTc2ODgxMzAwNCwiaWQiOiJzeXdiaGVjbmg0NnJobTAiLCJ0eXBlIjoiZmlsZSJ9.WHf_Yt9X9L-hdXzpY3ZOQa89JQ9rrKb56ni9AAQt0AY",
|
|
BeforeTestFunc: func(t testing.TB, app *tests.TestApp, e *core.ServeEvent) {
|
|
if err := createTestBackups(app); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
},
|
|
ExpectedStatus: 403,
|
|
ExpectedContent: []string{`"data":{}`},
|
|
ExpectedEvents: map[string]int{"*": 0},
|
|
},
|
|
{
|
|
Name: "with valid superuser file token but missing backup name",
|
|
Method: http.MethodGet,
|
|
URL: "/v1/backups/missing?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjb2xsZWN0aW9uSWQiOiJoYmNfMzE0MjYzNTgyMyIsImV4cCI6NDkyMjQxNjYwNCwiaWQiOiJzeXdiaGVjbmg0NnJobTAiLCJ0eXBlIjoiZmlsZSJ9.OuICrgrFGLFWo3zuC0-_Lm3VO3N8U03mOYoDZB7vQp8",
|
|
BeforeTestFunc: func(t testing.TB, app *tests.TestApp, e *core.ServeEvent) {
|
|
if err := createTestBackups(app); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
},
|
|
ExpectedStatus: 404,
|
|
ExpectedContent: []string{`"data":{}`},
|
|
ExpectedEvents: map[string]int{"*": 0},
|
|
},
|
|
{
|
|
Name: "with valid superuser file token",
|
|
Method: http.MethodGet,
|
|
URL: "/v1/backups/test1.zip?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjb2xsZWN0aW9uSWQiOiJoYmNfMzE0MjYzNTgyMyIsImV4cCI6NDkyMjQxNjYwNCwiaWQiOiJzeXdiaGVjbmg0NnJobTAiLCJ0eXBlIjoiZmlsZSJ9.OuICrgrFGLFWo3zuC0-_Lm3VO3N8U03mOYoDZB7vQp8",
|
|
BeforeTestFunc: func(t testing.TB, app *tests.TestApp, e *core.ServeEvent) {
|
|
if err := createTestBackups(app); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
},
|
|
ExpectedStatus: 200,
|
|
ExpectedContent: []string{
|
|
"storage/",
|
|
"data.db",
|
|
"auxiliary.db",
|
|
},
|
|
ExpectedEvents: map[string]int{"*": 0},
|
|
},
|
|
{
|
|
Name: "with valid superuser file token and backup name with escaped char",
|
|
Method: http.MethodGet,
|
|
URL: "/v1/backups/%40test4.zip?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjb2xsZWN0aW9uSWQiOiJoYmNfMzE0MjYzNTgyMyIsImV4cCI6NDkyMjQxNjYwNCwiaWQiOiJzeXdiaGVjbmg0NnJobTAiLCJ0eXBlIjoiZmlsZSJ9.OuICrgrFGLFWo3zuC0-_Lm3VO3N8U03mOYoDZB7vQp8",
|
|
BeforeTestFunc: func(t testing.TB, app *tests.TestApp, e *core.ServeEvent) {
|
|
if err := createTestBackups(app); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
},
|
|
ExpectedStatus: 200,
|
|
ExpectedContent: []string{
|
|
"storage/",
|
|
"data.db",
|
|
"auxiliary.db",
|
|
},
|
|
ExpectedEvents: map[string]int{"*": 0},
|
|
},
|
|
}
|
|
|
|
for _, scenario := range scenarios {
|
|
scenario.Test(t)
|
|
}
|
|
}
|
|
|
|
func TestBackupsDelete(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
noTestBackupFilesChanges := func(t testing.TB, app *tests.TestApp) {
|
|
files, err := getBackupFiles(app)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
expected := 4
|
|
if total := len(files); total != expected {
|
|
t.Fatalf("Expected %d backup(s), got %d", expected, total)
|
|
}
|
|
}
|
|
|
|
scenarios := []tests.ApiScenario{
|
|
{
|
|
Name: "unauthorized",
|
|
Method: http.MethodDelete,
|
|
URL: "/v1/backups/test1.zip",
|
|
BeforeTestFunc: func(t testing.TB, app *tests.TestApp, e *core.ServeEvent) {
|
|
if err := createTestBackups(app); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
},
|
|
AfterTestFunc: func(t testing.TB, app *tests.TestApp, res *http.Response) {
|
|
noTestBackupFilesChanges(t, app)
|
|
},
|
|
ExpectedStatus: 401,
|
|
ExpectedContent: []string{`"data":{}`},
|
|
ExpectedEvents: map[string]int{"*": 0},
|
|
},
|
|
{
|
|
Name: "authorized as regular user",
|
|
Method: http.MethodDelete,
|
|
URL: "/v1/backups/test1.zip",
|
|
Headers: map[string]string{
|
|
"Authorization": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjb2xsZWN0aW9uSWQiOiJfdXNlcnNfYXV0aF8iLCJleHAiOjI1MjQ2MDQ0NjEsImlkIjoiNHExeGxjbG1mbG9rdTMzIiwicmVmcmVzaGFibGUiOnRydWUsInR5cGUiOiJhdXRoIn0.AuFTIzCsdLEy-5adFzpjZzbqAdTP6Iu9B1wPBAxLBgo",
|
|
},
|
|
BeforeTestFunc: func(t testing.TB, app *tests.TestApp, e *core.ServeEvent) {
|
|
if err := createTestBackups(app); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
},
|
|
AfterTestFunc: func(t testing.TB, app *tests.TestApp, res *http.Response) {
|
|
noTestBackupFilesChanges(t, app)
|
|
},
|
|
ExpectedStatus: 403,
|
|
ExpectedContent: []string{`"data":{}`},
|
|
ExpectedEvents: map[string]int{"*": 0},
|
|
},
|
|
{
|
|
Name: "authorized as superuser (missing file)",
|
|
Method: http.MethodDelete,
|
|
URL: "/v1/backups/missing.zip",
|
|
Headers: map[string]string{
|
|
"Authorization": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjb2xsZWN0aW9uSWQiOiJoYmNfMzE0MjYzNTgyMyIsImV4cCI6MjUyNDYwNDQ2MSwiaWQiOiJzeXdiaGVjbmg0NnJobTAiLCJyZWZyZXNoYWJsZSI6dHJ1ZSwidHlwZSI6ImF1dGgifQ.CXBf8BazmUeg2RnJW8OEs1UFYF41rbCMOa6YZa4wZio",
|
|
},
|
|
BeforeTestFunc: func(t testing.TB, app *tests.TestApp, e *core.ServeEvent) {
|
|
if err := createTestBackups(app); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
},
|
|
AfterTestFunc: func(t testing.TB, app *tests.TestApp, res *http.Response) {
|
|
noTestBackupFilesChanges(t, app)
|
|
},
|
|
ExpectedStatus: 400,
|
|
ExpectedContent: []string{`"data":{}`},
|
|
ExpectedEvents: map[string]int{"*": 0},
|
|
},
|
|
{
|
|
Name: "authorized as superuser (existing file with matching active backup)",
|
|
Method: http.MethodDelete,
|
|
URL: "/v1/backups/test1.zip",
|
|
Headers: map[string]string{
|
|
"Authorization": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjb2xsZWN0aW9uSWQiOiJoYmNfMzE0MjYzNTgyMyIsImV4cCI6MjUyNDYwNDQ2MSwiaWQiOiJzeXdiaGVjbmg0NnJobTAiLCJyZWZyZXNoYWJsZSI6dHJ1ZSwidHlwZSI6ImF1dGgifQ.CXBf8BazmUeg2RnJW8OEs1UFYF41rbCMOa6YZa4wZio",
|
|
},
|
|
BeforeTestFunc: func(t testing.TB, app *tests.TestApp, e *core.ServeEvent) {
|
|
if err := createTestBackups(app); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// mock active backup with the same name to delete
|
|
app.Store().Set(core.StoreKeyActiveBackup, "test1.zip")
|
|
},
|
|
AfterTestFunc: func(t testing.TB, app *tests.TestApp, res *http.Response) {
|
|
noTestBackupFilesChanges(t, app)
|
|
},
|
|
ExpectedStatus: 400,
|
|
ExpectedContent: []string{`"data":{}`},
|
|
ExpectedEvents: map[string]int{"*": 0},
|
|
},
|
|
{
|
|
Name: "authorized as superuser (existing file and no matching active backup)",
|
|
Method: http.MethodDelete,
|
|
URL: "/v1/backups/test1.zip",
|
|
Headers: map[string]string{
|
|
"Authorization": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjb2xsZWN0aW9uSWQiOiJoYmNfMzE0MjYzNTgyMyIsImV4cCI6MjUyNDYwNDQ2MSwiaWQiOiJzeXdiaGVjbmg0NnJobTAiLCJyZWZyZXNoYWJsZSI6dHJ1ZSwidHlwZSI6ImF1dGgifQ.CXBf8BazmUeg2RnJW8OEs1UFYF41rbCMOa6YZa4wZio",
|
|
},
|
|
BeforeTestFunc: func(t testing.TB, app *tests.TestApp, e *core.ServeEvent) {
|
|
if err := createTestBackups(app); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// mock active backup with different name
|
|
app.Store().Set(core.StoreKeyActiveBackup, "new.zip")
|
|
},
|
|
AfterTestFunc: func(t testing.TB, app *tests.TestApp, res *http.Response) {
|
|
files, err := getBackupFiles(app)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if total := len(files); total != 3 {
|
|
t.Fatalf("Expected %d backup files, got %d", 3, total)
|
|
}
|
|
|
|
deletedFile := "test1.zip"
|
|
|
|
for _, f := range files {
|
|
if f.Key == deletedFile {
|
|
t.Fatalf("Expected backup %q to be deleted", deletedFile)
|
|
}
|
|
}
|
|
},
|
|
ExpectedStatus: 204,
|
|
ExpectedEvents: map[string]int{"*": 0},
|
|
},
|
|
{
|
|
Name: "authorized as superuser (backup with escaped character)",
|
|
Method: http.MethodDelete,
|
|
URL: "/v1/backups/%40test4.zip",
|
|
Headers: map[string]string{
|
|
"Authorization": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjb2xsZWN0aW9uSWQiOiJoYmNfMzE0MjYzNTgyMyIsImV4cCI6MjUyNDYwNDQ2MSwiaWQiOiJzeXdiaGVjbmg0NnJobTAiLCJyZWZyZXNoYWJsZSI6dHJ1ZSwidHlwZSI6ImF1dGgifQ.CXBf8BazmUeg2RnJW8OEs1UFYF41rbCMOa6YZa4wZio",
|
|
},
|
|
BeforeTestFunc: func(t testing.TB, app *tests.TestApp, e *core.ServeEvent) {
|
|
if err := createTestBackups(app); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
},
|
|
AfterTestFunc: func(t testing.TB, app *tests.TestApp, res *http.Response) {
|
|
files, err := getBackupFiles(app)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if total := len(files); total != 3 {
|
|
t.Fatalf("Expected %d backup files, got %d", 3, total)
|
|
}
|
|
|
|
deletedFile := "@test4.zip"
|
|
|
|
for _, f := range files {
|
|
if f.Key == deletedFile {
|
|
t.Fatalf("Expected backup %q to be deleted", deletedFile)
|
|
}
|
|
}
|
|
},
|
|
ExpectedStatus: 204,
|
|
ExpectedEvents: map[string]int{"*": 0},
|
|
},
|
|
}
|
|
|
|
for _, scenario := range scenarios {
|
|
scenario.Test(t)
|
|
}
|
|
}
|
|
|
|
func TestBackupsRestore(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
scenarios := []tests.ApiScenario{
|
|
{
|
|
Name: "unauthorized",
|
|
Method: http.MethodPost,
|
|
URL: "/v1/backups/test1.zip/restore",
|
|
BeforeTestFunc: func(t testing.TB, app *tests.TestApp, e *core.ServeEvent) {
|
|
if err := createTestBackups(app); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
},
|
|
ExpectedStatus: 401,
|
|
ExpectedContent: []string{`"data":{}`},
|
|
ExpectedEvents: map[string]int{"*": 0},
|
|
},
|
|
{
|
|
Name: "authorized as regular user",
|
|
Method: http.MethodPost,
|
|
URL: "/v1/backups/test1.zip/restore",
|
|
Headers: map[string]string{
|
|
"Authorization": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjb2xsZWN0aW9uSWQiOiJfdXNlcnNfYXV0aF8iLCJleHAiOjI1MjQ2MDQ0NjEsImlkIjoiNHExeGxjbG1mbG9rdTMzIiwicmVmcmVzaGFibGUiOnRydWUsInR5cGUiOiJhdXRoIn0.AuFTIzCsdLEy-5adFzpjZzbqAdTP6Iu9B1wPBAxLBgo",
|
|
},
|
|
BeforeTestFunc: func(t testing.TB, app *tests.TestApp, e *core.ServeEvent) {
|
|
if err := createTestBackups(app); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
},
|
|
ExpectedStatus: 403,
|
|
ExpectedContent: []string{`"data":{}`},
|
|
ExpectedEvents: map[string]int{"*": 0},
|
|
},
|
|
{
|
|
Name: "authorized as superuser (missing file)",
|
|
Method: http.MethodPost,
|
|
URL: "/v1/backups/missing.zip/restore",
|
|
Headers: map[string]string{
|
|
"Authorization": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjb2xsZWN0aW9uSWQiOiJoYmNfMzE0MjYzNTgyMyIsImV4cCI6MjUyNDYwNDQ2MSwiaWQiOiJzeXdiaGVjbmg0NnJobTAiLCJyZWZyZXNoYWJsZSI6dHJ1ZSwidHlwZSI6ImF1dGgifQ.CXBf8BazmUeg2RnJW8OEs1UFYF41rbCMOa6YZa4wZio",
|
|
},
|
|
BeforeTestFunc: func(t testing.TB, app *tests.TestApp, e *core.ServeEvent) {
|
|
if err := createTestBackups(app); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
},
|
|
ExpectedStatus: 400,
|
|
ExpectedContent: []string{`"data":{}`},
|
|
ExpectedEvents: map[string]int{"*": 0},
|
|
},
|
|
{
|
|
Name: "authorized as superuser (active backup process)",
|
|
Method: http.MethodPost,
|
|
URL: "/v1/backups/test1.zip/restore",
|
|
Headers: map[string]string{
|
|
"Authorization": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjb2xsZWN0aW9uSWQiOiJoYmNfMzE0MjYzNTgyMyIsImV4cCI6MjUyNDYwNDQ2MSwiaWQiOiJzeXdiaGVjbmg0NnJobTAiLCJyZWZyZXNoYWJsZSI6dHJ1ZSwidHlwZSI6ImF1dGgifQ.CXBf8BazmUeg2RnJW8OEs1UFYF41rbCMOa6YZa4wZio",
|
|
},
|
|
BeforeTestFunc: func(t testing.TB, app *tests.TestApp, e *core.ServeEvent) {
|
|
if err := createTestBackups(app); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
app.Store().Set(core.StoreKeyActiveBackup, "")
|
|
},
|
|
ExpectedStatus: 400,
|
|
ExpectedContent: []string{`"data":{}`},
|
|
ExpectedEvents: map[string]int{"*": 0},
|
|
},
|
|
}
|
|
|
|
for _, scenario := range scenarios {
|
|
scenario.Test(t)
|
|
}
|
|
}
|
|
|
|
// The reply that starts a restore says only that it started, because a restore
|
|
// that completes replaces the data directory and restarts the process. This is
|
|
// where the answer is.
|
|
func TestBackupsRestoreOutcome(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
superuserToken := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjb2xsZWN0aW9uSWQiOiJoYmNfMzE0MjYzNTgyMyIsImV4cCI6MjUyNDYwNDQ2MSwiaWQiOiJzeXdiaGVjbmg0NnJobTAiLCJyZWZyZXNoYWJsZSI6dHJ1ZSwidHlwZSI6ImF1dGgifQ.CXBf8BazmUeg2RnJW8OEs1UFYF41rbCMOa6YZa4wZio"
|
|
|
|
scenarios := []tests.ApiScenario{
|
|
{
|
|
Name: "unauthorized",
|
|
Method: http.MethodGet,
|
|
URL: "/v1/backups/test1.zip/restore",
|
|
ExpectedStatus: 401,
|
|
ExpectedContent: []string{`"data":{}`},
|
|
ExpectedEvents: map[string]int{"*": 0},
|
|
},
|
|
{
|
|
Name: "nothing attempted",
|
|
Method: http.MethodGet,
|
|
URL: "/v1/backups/test1.zip/restore",
|
|
Headers: map[string]string{"Authorization": superuserToken},
|
|
ExpectedStatus: 404,
|
|
ExpectedContent: []string{`"data":{}`},
|
|
ExpectedEvents: map[string]int{"*": 0},
|
|
},
|
|
{
|
|
Name: "still running",
|
|
Method: http.MethodGet,
|
|
URL: "/v1/backups/test1.zip/restore",
|
|
Headers: map[string]string{"Authorization": superuserToken},
|
|
BeforeTestFunc: func(t testing.TB, app *tests.TestApp, e *core.ServeEvent) {
|
|
app.Store().Set(core.StoreKeyActiveBackup, "test1.zip")
|
|
},
|
|
ExpectedStatus: 202,
|
|
ExpectedEvents: map[string]int{"*": 0},
|
|
},
|
|
{
|
|
Name: "stopped, with the reason",
|
|
Method: http.MethodGet,
|
|
URL: "/v1/backups/test1.zip/restore",
|
|
Headers: map[string]string{"Authorization": superuserToken},
|
|
BeforeTestFunc: func(t testing.TB, app *tests.TestApp, e *core.ServeEvent) {
|
|
app.Store().Set(core.StoreKeyRestoreFailure, core.RestoreFailure{
|
|
Time: types.NowDateTime(),
|
|
Name: "test1.zip",
|
|
Error: "data.db file is missing or invalid",
|
|
})
|
|
},
|
|
ExpectedStatus: 200,
|
|
ExpectedContent: []string{`"name":"test1.zip"`, `data.db file is missing or invalid`, `"time":`},
|
|
ExpectedEvents: map[string]int{"*": 0},
|
|
},
|
|
{
|
|
Name: "another backup stopped",
|
|
Method: http.MethodGet,
|
|
URL: "/v1/backups/test2.zip/restore",
|
|
Headers: map[string]string{"Authorization": superuserToken},
|
|
BeforeTestFunc: func(t testing.TB, app *tests.TestApp, e *core.ServeEvent) {
|
|
app.Store().Set(core.StoreKeyRestoreFailure, core.RestoreFailure{
|
|
Time: types.NowDateTime(),
|
|
Name: "test1.zip",
|
|
Error: "data.db file is missing or invalid",
|
|
})
|
|
},
|
|
ExpectedStatus: 404,
|
|
ExpectedContent: []string{`"data":{}`},
|
|
ExpectedEvents: map[string]int{"*": 0},
|
|
},
|
|
}
|
|
|
|
for _, scenario := range scenarios {
|
|
scenario.Test(t)
|
|
}
|
|
}
|
|
|
|
// -------------------------------------------------------------------
|
|
|
|
func createTestBackups(app core.App) error {
|
|
ctx := context.Background()
|
|
|
|
if err := app.CreateBackup(ctx, "test1.zip"); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := app.CreateBackup(ctx, "test2.zip"); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := app.CreateBackup(ctx, "test3.zip"); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := app.CreateBackup(ctx, "@test4.zip"); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func getBackupFiles(app core.App) ([]*blob.ListObject, error) {
|
|
fsys, err := app.NewBackupsFilesystem()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer fsys.Close()
|
|
|
|
return fsys.List("")
|
|
}
|
|
|
|
func ensureNoBackups(t testing.TB, app *tests.TestApp) {
|
|
files, err := getBackupFiles(app)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if total := len(files); total != 0 {
|
|
t.Fatalf("Expected 0 backup files, got %d", total)
|
|
}
|
|
}
|