package api import ( "bytes" "context" "encoding/json" "net/http" "net/http/httptest" "strings" "testing" "time" "git.fabledsword.com/bvandeusen/minstrel/internal/auth" "git.fabledsword.com/bvandeusen/minstrel/internal/backup" "git.fabledsword.com/bvandeusen/minstrel/internal/dbtest" ) // A new server takes a backup only with the setup token from its log, only // while it has no accounts, and only a file that is a backup; then the // backup's accounts sign in again. func TestRestore_ANewServerTakesABackupWithTheSetupToken(t *testing.T) { h, pool := testHandlers(t) ctx := context.Background() truncateLibrary(t, pool) resetUsers(t, h) seedUser(t, pool, "restored", "abcd1234", true) c, err := backup.Snapshot(ctx, pool) if err != nil { t.Fatal(err) } var file bytes.Buffer if _, err := backup.Write(&file, c, time.Now(), "test"); err != nil { t.Fatal(err) } resetUsers(t, h) token, err := auth.NewSetupToken() if err != nil { t.Fatal(err) } h.setupToken = token h.requireSetupToken = true restore := func(setupToken string, body []byte) *httptest.ResponseRecorder { req := httptest.NewRequest(http.MethodPost, "/api/auth/restore", bytes.NewReader(body)) req.Header.Set("Content-Type", "application/x-tar") if setupToken != "" { req.Header.Set(SetupTokenHeader, setupToken) } rec := httptest.NewRecorder() h.handleRestore(rec, req) return rec } if w := restore("", file.Bytes()); w.Code != http.StatusForbidden { t.Errorf("no setup token: status = %d, want 403", w.Code) } if w := restore("wrong", file.Bytes()); w.Code != http.StatusForbidden { t.Errorf("a wrong setup token: status = %d, want 403", w.Code) } if w := restore(token.Value(), []byte("not a tar file")); w.Code != http.StatusBadRequest || !strings.Contains(w.Body.String(), "not_a_backup") { t.Errorf("not a backup: status = %d body = %s, want 400 not_a_backup", w.Code, w.Body.String()) } var n int if err := pool.QueryRow(ctx, "SELECT count(*) FROM users").Scan(&n); err != nil || n != 0 { t.Fatalf("a refused restore left %d accounts (%v)", n, err) } w := restore(token.Value(), file.Bytes()) if w.Code != http.StatusOK { t.Fatalf("restore: status = %d body = %s", w.Code, w.Body.String()) } var out restoreResp if err := json.NewDecoder(w.Body).Decode(&out); err != nil || out.Accounts != 1 { t.Errorf("restore answered %+v (%v), want one account", out, err) } var admin bool if err := pool.QueryRow(ctx, "SELECT is_admin FROM users WHERE username = $1", dbtest.TestUserPrefix+"restored").Scan(&admin); err != nil || !admin { t.Errorf("the restored admin: is_admin = %v, %v", admin, err) } if w := restore(token.Value(), file.Bytes()); w.Code != http.StatusConflict || !strings.Contains(w.Body.String(), "not_empty") { t.Errorf("a second restore: status = %d body = %s, want 409 not_empty", w.Code, w.Body.String()) } }