mirror of
https://github.com/databasus/databasus.git
synced 2026-04-06 00:32:03 +02:00
898 lines
25 KiB
Go
898 lines
25 KiB
Go
package restores
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"log/slog"
|
|
"net/http"
|
|
"strconv"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/google/uuid"
|
|
"github.com/stretchr/testify/assert"
|
|
|
|
env_config "databasus-backend/internal/config"
|
|
audit_logs "databasus-backend/internal/features/audit_logs"
|
|
backups_controllers "databasus-backend/internal/features/backups/backups/controllers"
|
|
backups_core "databasus-backend/internal/features/backups/backups/core"
|
|
backups_config "databasus-backend/internal/features/backups/config"
|
|
"databasus-backend/internal/features/databases"
|
|
"databasus-backend/internal/features/databases/databases/mysql"
|
|
"databasus-backend/internal/features/databases/databases/postgresql"
|
|
"databasus-backend/internal/features/notifiers"
|
|
restores_core "databasus-backend/internal/features/restores/core"
|
|
"databasus-backend/internal/features/restores/restoring"
|
|
"databasus-backend/internal/features/storages"
|
|
local_storage "databasus-backend/internal/features/storages/models/local"
|
|
tasks_cancellation "databasus-backend/internal/features/tasks/cancellation"
|
|
users_dto "databasus-backend/internal/features/users/dto"
|
|
users_enums "databasus-backend/internal/features/users/enums"
|
|
users_testing "databasus-backend/internal/features/users/testing"
|
|
workspaces_models "databasus-backend/internal/features/workspaces/models"
|
|
workspaces_testing "databasus-backend/internal/features/workspaces/testing"
|
|
cache_utils "databasus-backend/internal/util/cache"
|
|
util_encryption "databasus-backend/internal/util/encryption"
|
|
test_utils "databasus-backend/internal/util/testing"
|
|
"databasus-backend/internal/util/tools"
|
|
)
|
|
|
|
func Test_GetRestores_WhenUserIsWorkspaceMember_RestoresReturned(t *testing.T) {
|
|
router := createTestRouter()
|
|
owner := users_testing.CreateTestUser(users_enums.UserRoleMember)
|
|
workspace := workspaces_testing.CreateTestWorkspace("Test Workspace", owner, router)
|
|
defer workspaces_testing.RemoveTestWorkspace(workspace, router)
|
|
|
|
database, backup := createTestDatabaseWithBackupForRestore(workspace, owner, router)
|
|
defer cleanupDatabaseWithBackup(database, backup)
|
|
|
|
var restores []*restores_core.Restore
|
|
test_utils.MakeGetRequestAndUnmarshal(
|
|
t,
|
|
router,
|
|
fmt.Sprintf("/api/v1/restores/%s", backup.ID.String()),
|
|
"Bearer "+owner.Token,
|
|
http.StatusOK,
|
|
&restores,
|
|
)
|
|
|
|
assert.NotNil(t, restores)
|
|
assert.Equal(t, 0, len(restores))
|
|
assert.NotNil(t, database)
|
|
}
|
|
|
|
func Test_GetRestores_WhenUserIsNotWorkspaceMember_ReturnsForbidden(t *testing.T) {
|
|
router := createTestRouter()
|
|
owner := users_testing.CreateTestUser(users_enums.UserRoleMember)
|
|
workspace := workspaces_testing.CreateTestWorkspace("Test Workspace", owner, router)
|
|
defer workspaces_testing.RemoveTestWorkspace(workspace, router)
|
|
|
|
database, backup := createTestDatabaseWithBackupForRestore(workspace, owner, router)
|
|
defer cleanupDatabaseWithBackup(database, backup)
|
|
|
|
nonMember := users_testing.CreateTestUser(users_enums.UserRoleMember)
|
|
|
|
testResp := test_utils.MakeGetRequest(
|
|
t,
|
|
router,
|
|
fmt.Sprintf("/api/v1/restores/%s", backup.ID.String()),
|
|
"Bearer "+nonMember.Token,
|
|
http.StatusBadRequest,
|
|
)
|
|
|
|
assert.Contains(t, string(testResp.Body), "insufficient permissions")
|
|
}
|
|
|
|
func Test_GetRestores_WhenUserIsGlobalAdmin_RestoresReturned(t *testing.T) {
|
|
router := createTestRouter()
|
|
owner := users_testing.CreateTestUser(users_enums.UserRoleMember)
|
|
workspace := workspaces_testing.CreateTestWorkspace("Test Workspace", owner, router)
|
|
defer workspaces_testing.RemoveTestWorkspace(workspace, router)
|
|
|
|
database, backup := createTestDatabaseWithBackupForRestore(workspace, owner, router)
|
|
defer cleanupDatabaseWithBackup(database, backup)
|
|
|
|
admin := users_testing.CreateTestUser(users_enums.UserRoleAdmin)
|
|
|
|
var restores []*restores_core.Restore
|
|
test_utils.MakeGetRequestAndUnmarshal(
|
|
t,
|
|
router,
|
|
fmt.Sprintf("/api/v1/restores/%s", backup.ID.String()),
|
|
"Bearer "+admin.Token,
|
|
http.StatusOK,
|
|
&restores,
|
|
)
|
|
|
|
assert.NotNil(t, restores)
|
|
}
|
|
|
|
func Test_RestoreBackup_WhenUserIsWorkspaceMember_RestoreInitiated(t *testing.T) {
|
|
router := createTestRouter()
|
|
|
|
_, cleanup := SetupMockRestoreNode(t)
|
|
defer cleanup()
|
|
|
|
owner := users_testing.CreateTestUser(users_enums.UserRoleMember)
|
|
workspace := workspaces_testing.CreateTestWorkspace("Test Workspace", owner, router)
|
|
defer workspaces_testing.RemoveTestWorkspace(workspace, router)
|
|
|
|
database, backup := createTestDatabaseWithBackupForRestore(workspace, owner, router)
|
|
defer cleanupDatabaseWithBackup(database, backup)
|
|
|
|
request := restores_core.RestoreBackupRequest{
|
|
PostgresqlDatabase: &postgresql.PostgresqlDatabase{
|
|
Version: tools.PostgresqlVersion16,
|
|
Host: env_config.GetEnv().TestLocalhost,
|
|
Port: 5432,
|
|
Username: "postgres",
|
|
Password: "postgres",
|
|
},
|
|
}
|
|
|
|
testResp := test_utils.MakePostRequest(
|
|
t,
|
|
router,
|
|
fmt.Sprintf("/api/v1/restores/%s/restore", backup.ID.String()),
|
|
"Bearer "+owner.Token,
|
|
request,
|
|
http.StatusOK,
|
|
)
|
|
|
|
assert.Contains(t, string(testResp.Body), "restore started successfully")
|
|
}
|
|
|
|
func Test_RestoreBackup_WhenUserIsNotWorkspaceMember_ReturnsForbidden(t *testing.T) {
|
|
router := createTestRouter()
|
|
owner := users_testing.CreateTestUser(users_enums.UserRoleMember)
|
|
workspace := workspaces_testing.CreateTestWorkspace("Test Workspace", owner, router)
|
|
defer workspaces_testing.RemoveTestWorkspace(workspace, router)
|
|
|
|
database, backup := createTestDatabaseWithBackupForRestore(workspace, owner, router)
|
|
defer cleanupDatabaseWithBackup(database, backup)
|
|
|
|
nonMember := users_testing.CreateTestUser(users_enums.UserRoleMember)
|
|
|
|
request := restores_core.RestoreBackupRequest{
|
|
PostgresqlDatabase: &postgresql.PostgresqlDatabase{
|
|
Version: tools.PostgresqlVersion16,
|
|
Host: env_config.GetEnv().TestLocalhost,
|
|
Port: 5432,
|
|
Username: "postgres",
|
|
Password: "postgres",
|
|
},
|
|
}
|
|
|
|
testResp := test_utils.MakePostRequest(
|
|
t,
|
|
router,
|
|
fmt.Sprintf("/api/v1/restores/%s/restore", backup.ID.String()),
|
|
"Bearer "+nonMember.Token,
|
|
request,
|
|
http.StatusBadRequest,
|
|
)
|
|
|
|
assert.Contains(t, string(testResp.Body), "insufficient permissions")
|
|
}
|
|
|
|
func Test_RestoreBackup_WithIsExcludeExtensions_FlagPassedCorrectly(t *testing.T) {
|
|
router := createTestRouter()
|
|
|
|
_, cleanup := SetupMockRestoreNode(t)
|
|
defer cleanup()
|
|
|
|
owner := users_testing.CreateTestUser(users_enums.UserRoleMember)
|
|
workspace := workspaces_testing.CreateTestWorkspace("Test Workspace", owner, router)
|
|
defer workspaces_testing.RemoveTestWorkspace(workspace, router)
|
|
|
|
database, backup := createTestDatabaseWithBackupForRestore(workspace, owner, router)
|
|
defer cleanupDatabaseWithBackup(database, backup)
|
|
|
|
request := restores_core.RestoreBackupRequest{
|
|
PostgresqlDatabase: &postgresql.PostgresqlDatabase{
|
|
Version: tools.PostgresqlVersion16,
|
|
Host: env_config.GetEnv().TestLocalhost,
|
|
Port: 5432,
|
|
Username: "postgres",
|
|
Password: "postgres",
|
|
IsExcludeExtensions: true,
|
|
},
|
|
}
|
|
|
|
testResp := test_utils.MakePostRequest(
|
|
t,
|
|
router,
|
|
fmt.Sprintf("/api/v1/restores/%s/restore", backup.ID.String()),
|
|
"Bearer "+owner.Token,
|
|
request,
|
|
http.StatusOK,
|
|
)
|
|
|
|
assert.Contains(t, string(testResp.Body), "restore started successfully")
|
|
}
|
|
|
|
func Test_RestoreBackup_AuditLogWritten(t *testing.T) {
|
|
router := createTestRouter()
|
|
|
|
_, cleanup := SetupMockRestoreNode(t)
|
|
defer cleanup()
|
|
|
|
owner := users_testing.CreateTestUser(users_enums.UserRoleMember)
|
|
workspace := workspaces_testing.CreateTestWorkspace("Test Workspace", owner, router)
|
|
defer workspaces_testing.RemoveTestWorkspace(workspace, router)
|
|
|
|
database, backup := createTestDatabaseWithBackupForRestore(workspace, owner, router)
|
|
defer cleanupDatabaseWithBackup(database, backup)
|
|
|
|
request := restores_core.RestoreBackupRequest{
|
|
PostgresqlDatabase: &postgresql.PostgresqlDatabase{
|
|
Version: tools.PostgresqlVersion16,
|
|
Host: env_config.GetEnv().TestLocalhost,
|
|
Port: 5432,
|
|
Username: "postgres",
|
|
Password: "postgres",
|
|
},
|
|
}
|
|
|
|
test_utils.MakePostRequest(
|
|
t,
|
|
router,
|
|
fmt.Sprintf("/api/v1/restores/%s/restore", backup.ID.String()),
|
|
"Bearer "+owner.Token,
|
|
request,
|
|
http.StatusOK,
|
|
)
|
|
|
|
time.Sleep(100 * time.Millisecond)
|
|
|
|
auditLogService := audit_logs.GetAuditLogService()
|
|
auditLogs, err := auditLogService.GetWorkspaceAuditLogs(
|
|
workspace.ID,
|
|
&audit_logs.GetAuditLogsRequest{
|
|
Limit: 100,
|
|
Offset: 0,
|
|
},
|
|
)
|
|
assert.NoError(t, err)
|
|
|
|
found := false
|
|
for _, log := range auditLogs.AuditLogs {
|
|
if strings.Contains(log.Message, "Database restored for database") &&
|
|
strings.Contains(log.Message, database.Name) {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
assert.True(t, found, "Audit log for restore not found")
|
|
}
|
|
|
|
func Test_RestoreBackup_DiskSpaceValidation(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
dbType databases.DatabaseType
|
|
cpuCount int
|
|
expectDiskValidated bool
|
|
}{
|
|
{
|
|
name: "PostgreSQL_CPU4_SpaceValidated",
|
|
dbType: databases.DatabaseTypePostgres,
|
|
cpuCount: 4,
|
|
expectDiskValidated: true,
|
|
},
|
|
{
|
|
name: "PostgreSQL_CPU1_SpaceNotValidated",
|
|
dbType: databases.DatabaseTypePostgres,
|
|
cpuCount: 1,
|
|
expectDiskValidated: false,
|
|
},
|
|
{
|
|
name: "MySQL_SpaceNotValidated",
|
|
dbType: databases.DatabaseTypeMysql,
|
|
cpuCount: 3,
|
|
expectDiskValidated: false,
|
|
},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
router := createTestRouter()
|
|
|
|
// Setup mock node for tests that skip disk validation and reach scheduler
|
|
if !tc.expectDiskValidated {
|
|
_, cleanup := SetupMockRestoreNode(t)
|
|
defer cleanup()
|
|
}
|
|
|
|
owner := users_testing.CreateTestUser(users_enums.UserRoleMember)
|
|
workspace := workspaces_testing.CreateTestWorkspace("Test Workspace", owner, router)
|
|
defer workspaces_testing.RemoveTestWorkspace(workspace, router)
|
|
|
|
var database *databases.Database
|
|
var backup *backups_core.Backup
|
|
var storage *storages.Storage
|
|
var request restores_core.RestoreBackupRequest
|
|
|
|
if tc.dbType == databases.DatabaseTypePostgres {
|
|
database, backup = createTestDatabaseWithBackupForRestore(workspace, owner, router)
|
|
defer cleanupDatabaseWithBackup(database, backup)
|
|
request = restores_core.RestoreBackupRequest{
|
|
PostgresqlDatabase: &postgresql.PostgresqlDatabase{
|
|
Version: tools.PostgresqlVersion16,
|
|
Host: env_config.GetEnv().TestLocalhost,
|
|
Port: 5432,
|
|
Username: "postgres",
|
|
Password: "postgres",
|
|
CpuCount: tc.cpuCount,
|
|
},
|
|
}
|
|
} else {
|
|
mysqlDB := createTestMySQLDatabase(
|
|
"Test MySQL DB",
|
|
workspace.ID,
|
|
owner.Token,
|
|
router,
|
|
)
|
|
database = mysqlDB
|
|
storage = createTestStorage(workspace.ID)
|
|
|
|
defer func() {
|
|
// Cleanup in dependency order: backup -> database -> storage
|
|
cleanupBackup(backup)
|
|
databases.RemoveTestDatabase(mysqlDB)
|
|
time.Sleep(50 * time.Millisecond)
|
|
storages.RemoveTestStorage(storage.ID)
|
|
}()
|
|
|
|
configService := backups_config.GetBackupConfigService()
|
|
config, err := configService.GetBackupConfigByDbId(mysqlDB.ID)
|
|
assert.NoError(t, err)
|
|
|
|
config.IsBackupsEnabled = true
|
|
config.StorageID = &storage.ID
|
|
config.Storage = storage
|
|
_, err = configService.SaveBackupConfig(config)
|
|
assert.NoError(t, err)
|
|
|
|
backup = createTestBackup(mysqlDB, storage)
|
|
|
|
request = restores_core.RestoreBackupRequest{
|
|
MysqlDatabase: &mysql.MysqlDatabase{
|
|
Version: tools.MysqlVersion80,
|
|
Host: env_config.GetEnv().TestLocalhost,
|
|
Port: 3306,
|
|
Username: "root",
|
|
Password: "password",
|
|
},
|
|
}
|
|
}
|
|
|
|
// Set huge backup size (10 TB) that would fail disk validation if checked
|
|
repo := &backups_core.BackupRepository{}
|
|
backup.BackupSizeMb = 10485760.0
|
|
err := repo.Save(backup)
|
|
assert.NoError(t, err)
|
|
|
|
expectedStatus := http.StatusOK
|
|
if tc.expectDiskValidated {
|
|
expectedStatus = http.StatusBadRequest
|
|
}
|
|
|
|
testResp := test_utils.MakePostRequest(
|
|
t,
|
|
router,
|
|
fmt.Sprintf("/api/v1/restores/%s/restore", backup.ID.String()),
|
|
"Bearer "+owner.Token,
|
|
request,
|
|
expectedStatus,
|
|
)
|
|
|
|
bodyStr := string(testResp.Body)
|
|
if tc.expectDiskValidated {
|
|
assert.Contains(t, bodyStr, "is required")
|
|
assert.Contains(t, bodyStr, "is available")
|
|
assert.Contains(t, bodyStr, "disk space")
|
|
} else {
|
|
assert.Contains(t, bodyStr, "restore started successfully")
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_CancelRestore_InProgressRestore_SuccessfullyCancelled(t *testing.T) {
|
|
cache_utils.ClearAllCache()
|
|
tasks_cancellation.SetupDependencies()
|
|
|
|
user := users_testing.CreateTestUser(users_enums.UserRoleAdmin)
|
|
router := createTestRouter()
|
|
workspace := workspaces_testing.CreateTestWorkspace("Test Workspace", user, router)
|
|
storage := storages.CreateTestStorage(workspace.ID)
|
|
notifier := notifiers.CreateTestNotifier(workspace.ID)
|
|
database := databases.CreateTestDatabase(workspace.ID, storage, notifier)
|
|
|
|
defer func() {
|
|
backupRepo := backups_core.BackupRepository{}
|
|
backups, _ := backupRepo.FindByDatabaseID(database.ID)
|
|
for _, backup := range backups {
|
|
backupRepo.DeleteByID(backup.ID)
|
|
}
|
|
|
|
restoreRepo := restores_core.RestoreRepository{}
|
|
restores, _ := restoreRepo.FindByStatus(restores_core.RestoreStatusInProgress)
|
|
for _, restore := range restores {
|
|
restoreRepo.DeleteByID(restore.ID)
|
|
}
|
|
restores, _ = restoreRepo.FindByStatus(restores_core.RestoreStatusCanceled)
|
|
for _, restore := range restores {
|
|
restoreRepo.DeleteByID(restore.ID)
|
|
}
|
|
|
|
databases.RemoveTestDatabase(database)
|
|
time.Sleep(50 * time.Millisecond)
|
|
storages.RemoveTestStorage(storage.ID)
|
|
notifiers.RemoveTestNotifier(notifier)
|
|
workspaces_testing.RemoveTestWorkspace(workspace, router)
|
|
|
|
cache_utils.ClearAllCache()
|
|
}()
|
|
|
|
backups_config.EnableBackupsForTestDatabase(database.ID, storage)
|
|
backup := backups_controllers.CreateTestBackup(database.ID, storage.ID)
|
|
|
|
mockUsecase := &restoring.MockBlockingRestoreUsecase{
|
|
StartedChan: make(chan bool, 1),
|
|
}
|
|
restorerNode := restoring.CreateTestRestorerNodeWithUsecase(mockUsecase)
|
|
|
|
cancelNode := restoring.StartRestorerNodeForTest(t, restorerNode)
|
|
defer cancelNode()
|
|
|
|
time.Sleep(200 * time.Millisecond)
|
|
|
|
restoreRequest := restores_core.RestoreBackupRequest{
|
|
PostgresqlDatabase: &postgresql.PostgresqlDatabase{
|
|
Version: tools.PostgresqlVersion16,
|
|
Host: env_config.GetEnv().TestLocalhost,
|
|
Port: 5432,
|
|
Username: "postgres",
|
|
Password: "postgres",
|
|
},
|
|
}
|
|
|
|
var restoreResponse map[string]interface{}
|
|
test_utils.MakePostRequestAndUnmarshal(
|
|
t,
|
|
router,
|
|
fmt.Sprintf("/api/v1/restores/%s/restore", backup.ID.String()),
|
|
"Bearer "+user.Token,
|
|
restoreRequest,
|
|
http.StatusOK,
|
|
&restoreResponse,
|
|
)
|
|
|
|
select {
|
|
case <-mockUsecase.StartedChan:
|
|
t.Log("Restore started and is blocking")
|
|
case <-time.After(2 * time.Second):
|
|
t.Fatal("Restore did not start within timeout")
|
|
}
|
|
|
|
restoreRepo := &restores_core.RestoreRepository{}
|
|
restores, err := restoreRepo.FindByBackupID(backup.ID)
|
|
assert.NoError(t, err)
|
|
assert.Greater(t, len(restores), 0, "At least one restore should exist")
|
|
|
|
var restoreID uuid.UUID
|
|
for _, r := range restores {
|
|
if r.Status == restores_core.RestoreStatusInProgress {
|
|
restoreID = r.ID
|
|
break
|
|
}
|
|
}
|
|
assert.NotEqual(t, uuid.Nil, restoreID, "Should find an in-progress restore")
|
|
|
|
resp := test_utils.MakePostRequest(
|
|
t,
|
|
router,
|
|
fmt.Sprintf("/api/v1/restores/cancel/%s", restoreID.String()),
|
|
"Bearer "+user.Token,
|
|
nil,
|
|
http.StatusNoContent,
|
|
)
|
|
|
|
assert.Equal(t, http.StatusNoContent, resp.StatusCode)
|
|
|
|
deadline := time.Now().UTC().Add(3 * time.Second)
|
|
var restore *restores_core.Restore
|
|
for time.Now().UTC().Before(deadline) {
|
|
restore, err = restoreRepo.FindByID(restoreID)
|
|
assert.NoError(t, err)
|
|
if restore.Status == restores_core.RestoreStatusCanceled {
|
|
break
|
|
}
|
|
time.Sleep(100 * time.Millisecond)
|
|
}
|
|
|
|
assert.Equal(t, restores_core.RestoreStatusCanceled, restore.Status)
|
|
|
|
auditLogService := audit_logs.GetAuditLogService()
|
|
auditLogs, err := auditLogService.GetWorkspaceAuditLogs(
|
|
workspace.ID,
|
|
&audit_logs.GetAuditLogsRequest{Limit: 100, Offset: 0},
|
|
)
|
|
assert.NoError(t, err)
|
|
|
|
foundCancelLog := false
|
|
for _, log := range auditLogs.AuditLogs {
|
|
if strings.Contains(log.Message, "Restore cancelled") &&
|
|
strings.Contains(log.Message, database.Name) {
|
|
foundCancelLog = true
|
|
break
|
|
}
|
|
}
|
|
assert.True(t, foundCancelLog, "Cancel audit log should be created")
|
|
|
|
time.Sleep(200 * time.Millisecond)
|
|
}
|
|
|
|
func Test_RestoreBackup_WithParallelRestoreInProgress_ReturnsError(t *testing.T) {
|
|
router := createTestRouter()
|
|
|
|
_, cleanup := SetupMockRestoreNode(t)
|
|
defer cleanup()
|
|
|
|
owner := users_testing.CreateTestUser(users_enums.UserRoleMember)
|
|
workspace := workspaces_testing.CreateTestWorkspace("Test Workspace", owner, router)
|
|
defer workspaces_testing.RemoveTestWorkspace(workspace, router)
|
|
|
|
database, backup := createTestDatabaseWithBackupForRestore(workspace, owner, router)
|
|
defer cleanupDatabaseWithBackup(database, backup)
|
|
|
|
request := restores_core.RestoreBackupRequest{
|
|
PostgresqlDatabase: &postgresql.PostgresqlDatabase{
|
|
Version: tools.PostgresqlVersion16,
|
|
Host: env_config.GetEnv().TestLocalhost,
|
|
Port: 5432,
|
|
Username: "postgres",
|
|
Password: "postgres",
|
|
},
|
|
}
|
|
|
|
testResp := test_utils.MakePostRequest(
|
|
t,
|
|
router,
|
|
fmt.Sprintf("/api/v1/restores/%s/restore", backup.ID.String()),
|
|
"Bearer "+owner.Token,
|
|
request,
|
|
http.StatusOK,
|
|
)
|
|
assert.Contains(t, string(testResp.Body), "restore started successfully")
|
|
|
|
testResp2 := test_utils.MakePostRequest(
|
|
t,
|
|
router,
|
|
fmt.Sprintf("/api/v1/restores/%s/restore", backup.ID.String()),
|
|
"Bearer "+owner.Token,
|
|
request,
|
|
http.StatusBadRequest,
|
|
)
|
|
|
|
assert.Contains(t, string(testResp2.Body), "another restore is already in progress")
|
|
}
|
|
|
|
func createTestRouter() *gin.Engine {
|
|
return CreateTestRouter()
|
|
}
|
|
|
|
func createTestDatabaseWithBackupForRestore(
|
|
workspace *workspaces_models.Workspace,
|
|
owner *users_dto.SignInResponseDTO,
|
|
router *gin.Engine,
|
|
) (*databases.Database, *backups_core.Backup) {
|
|
database := createTestDatabase("Test Database", workspace.ID, owner.Token, router)
|
|
storage := createTestStorage(workspace.ID)
|
|
|
|
configService := backups_config.GetBackupConfigService()
|
|
config, err := configService.GetBackupConfigByDbId(database.ID)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
config.IsBackupsEnabled = true
|
|
config.StorageID = &storage.ID
|
|
config.Storage = storage
|
|
_, err = configService.SaveBackupConfig(config)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
backup := createTestBackup(database, storage)
|
|
|
|
return database, backup
|
|
}
|
|
|
|
func createTestDatabase(
|
|
name string,
|
|
workspaceID uuid.UUID,
|
|
token string,
|
|
router *gin.Engine,
|
|
) *databases.Database {
|
|
request := databases.Database{
|
|
WorkspaceID: &workspaceID,
|
|
Name: name,
|
|
Type: databases.DatabaseTypePostgres,
|
|
Postgresql: databases.GetTestPostgresConfig(),
|
|
}
|
|
|
|
w := workspaces_testing.MakeAPIRequest(
|
|
router,
|
|
"POST",
|
|
"/api/v1/databases/create",
|
|
"Bearer "+token,
|
|
request,
|
|
)
|
|
|
|
if w.Code != http.StatusCreated {
|
|
panic(
|
|
fmt.Sprintf("Failed to create database. Status: %d, Body: %s", w.Code, w.Body.String()),
|
|
)
|
|
}
|
|
|
|
var database databases.Database
|
|
if err := json.Unmarshal(w.Body.Bytes(), &database); err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
return &database
|
|
}
|
|
|
|
func createTestMySQLDatabase(
|
|
name string,
|
|
workspaceID uuid.UUID,
|
|
token string,
|
|
router *gin.Engine,
|
|
) *databases.Database {
|
|
env := env_config.GetEnv()
|
|
portStr := env.TestMysql80Port
|
|
if portStr == "" {
|
|
portStr = "33080"
|
|
}
|
|
|
|
port, err := strconv.Atoi(portStr)
|
|
if err != nil {
|
|
panic(fmt.Sprintf("Failed to parse TEST_MYSQL_80_PORT: %v", err))
|
|
}
|
|
|
|
testDbName := "testdb"
|
|
request := databases.Database{
|
|
WorkspaceID: &workspaceID,
|
|
Name: name,
|
|
Type: databases.DatabaseTypeMysql,
|
|
Mysql: &mysql.MysqlDatabase{
|
|
Version: tools.MysqlVersion80,
|
|
Host: env_config.GetEnv().TestLocalhost,
|
|
Port: port,
|
|
Username: "testuser",
|
|
Password: "testpassword",
|
|
Database: &testDbName,
|
|
},
|
|
}
|
|
|
|
w := workspaces_testing.MakeAPIRequest(
|
|
router,
|
|
"POST",
|
|
"/api/v1/databases/create",
|
|
"Bearer "+token,
|
|
request,
|
|
)
|
|
|
|
if w.Code != http.StatusCreated {
|
|
panic(
|
|
fmt.Sprintf(
|
|
"Failed to create MySQL database. Status: %d, Body: %s",
|
|
w.Code,
|
|
w.Body.String(),
|
|
),
|
|
)
|
|
}
|
|
|
|
var database databases.Database
|
|
if err := json.Unmarshal(w.Body.Bytes(), &database); err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
return &database
|
|
}
|
|
|
|
func createTestStorage(workspaceID uuid.UUID) *storages.Storage {
|
|
storage := &storages.Storage{
|
|
WorkspaceID: workspaceID,
|
|
Type: storages.StorageTypeLocal,
|
|
Name: "Test Storage " + uuid.New().String(),
|
|
LocalStorage: &local_storage.LocalStorage{},
|
|
}
|
|
|
|
repo := &storages.StorageRepository{}
|
|
storage, err := repo.Save(storage)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
return storage
|
|
}
|
|
|
|
func createTestBackup(
|
|
database *databases.Database,
|
|
storage *storages.Storage,
|
|
) *backups_core.Backup {
|
|
fieldEncryptor := util_encryption.GetFieldEncryptor()
|
|
|
|
backup := &backups_core.Backup{
|
|
ID: uuid.New(),
|
|
DatabaseID: database.ID,
|
|
StorageID: storage.ID,
|
|
Status: backups_core.BackupStatusCompleted,
|
|
BackupSizeMb: 10.5,
|
|
BackupDurationMs: 1000,
|
|
CreatedAt: time.Now().UTC(),
|
|
}
|
|
|
|
repo := &backups_core.BackupRepository{}
|
|
if err := repo.Save(backup); err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
dummyContent := []byte("dummy backup content for testing")
|
|
reader := strings.NewReader(string(dummyContent))
|
|
logger := slog.New(slog.NewTextHandler(io.Discard, nil))
|
|
if err := storage.SaveFile(
|
|
context.Background(),
|
|
fieldEncryptor,
|
|
logger,
|
|
backup.ID.String(),
|
|
reader,
|
|
); err != nil {
|
|
panic(fmt.Sprintf("Failed to create test backup file: %v", err))
|
|
}
|
|
|
|
return backup
|
|
}
|
|
|
|
func cleanupDatabaseWithBackup(database *databases.Database, backup *backups_core.Backup) {
|
|
// Clean up in reverse dependency order
|
|
cleanupBackup(backup)
|
|
databases.RemoveTestDatabase(database)
|
|
time.Sleep(50 * time.Millisecond)
|
|
|
|
// Clean up storage last (after database and backup are removed)
|
|
configService := backups_config.GetBackupConfigService()
|
|
config, err := configService.GetBackupConfigByDbId(database.ID)
|
|
if err == nil && config.StorageID != nil {
|
|
storages.RemoveTestStorage(*config.StorageID)
|
|
}
|
|
}
|
|
|
|
func Test_RestoreBackup_WhenCloudAndCpuCountMoreThanOne_ReturnsBadRequest(t *testing.T) {
|
|
router := createTestRouter()
|
|
owner := users_testing.CreateTestUser(users_enums.UserRoleMember)
|
|
workspace := workspaces_testing.CreateTestWorkspace("Test Workspace", owner, router)
|
|
defer workspaces_testing.RemoveTestWorkspace(workspace, router)
|
|
|
|
database, backup := createTestDatabaseWithBackupForRestore(workspace, owner, router)
|
|
defer cleanupDatabaseWithBackup(database, backup)
|
|
|
|
enableCloud(t)
|
|
|
|
request := restores_core.RestoreBackupRequest{
|
|
PostgresqlDatabase: &postgresql.PostgresqlDatabase{
|
|
Version: tools.PostgresqlVersion16,
|
|
Host: env_config.GetEnv().TestLocalhost,
|
|
Port: 5432,
|
|
Username: "postgres",
|
|
Password: "postgres",
|
|
CpuCount: 4,
|
|
},
|
|
}
|
|
|
|
testResp := test_utils.MakePostRequest(
|
|
t,
|
|
router,
|
|
fmt.Sprintf("/api/v1/restores/%s/restore", backup.ID.String()),
|
|
"Bearer "+owner.Token,
|
|
request,
|
|
http.StatusBadRequest,
|
|
)
|
|
|
|
assert.Contains(t, string(testResp.Body), "multi-thread restore is not supported in cloud mode")
|
|
}
|
|
|
|
func Test_RestoreBackup_WhenCloudAndCpuCountIsOne_RestoreInitiated(t *testing.T) {
|
|
router := createTestRouter()
|
|
|
|
_, cleanup := SetupMockRestoreNode(t)
|
|
defer cleanup()
|
|
|
|
owner := users_testing.CreateTestUser(users_enums.UserRoleMember)
|
|
workspace := workspaces_testing.CreateTestWorkspace("Test Workspace", owner, router)
|
|
defer workspaces_testing.RemoveTestWorkspace(workspace, router)
|
|
|
|
database, backup := createTestDatabaseWithBackupForRestore(workspace, owner, router)
|
|
defer cleanupDatabaseWithBackup(database, backup)
|
|
|
|
enableCloud(t)
|
|
|
|
request := restores_core.RestoreBackupRequest{
|
|
PostgresqlDatabase: &postgresql.PostgresqlDatabase{
|
|
Version: tools.PostgresqlVersion16,
|
|
Host: env_config.GetEnv().TestLocalhost,
|
|
Port: 5432,
|
|
Username: "postgres",
|
|
Password: "postgres",
|
|
CpuCount: 1,
|
|
},
|
|
}
|
|
|
|
testResp := test_utils.MakePostRequest(
|
|
t,
|
|
router,
|
|
fmt.Sprintf("/api/v1/restores/%s/restore", backup.ID.String()),
|
|
"Bearer "+owner.Token,
|
|
request,
|
|
http.StatusOK,
|
|
)
|
|
|
|
assert.Contains(t, string(testResp.Body), "restore started successfully")
|
|
}
|
|
|
|
func Test_RestoreBackup_WhenNotCloudAndCpuCountMoreThanOne_RestoreInitiated(t *testing.T) {
|
|
router := createTestRouter()
|
|
|
|
_, cleanup := SetupMockRestoreNode(t)
|
|
defer cleanup()
|
|
|
|
owner := users_testing.CreateTestUser(users_enums.UserRoleMember)
|
|
workspace := workspaces_testing.CreateTestWorkspace("Test Workspace", owner, router)
|
|
defer workspaces_testing.RemoveTestWorkspace(workspace, router)
|
|
|
|
database, backup := createTestDatabaseWithBackupForRestore(workspace, owner, router)
|
|
defer cleanupDatabaseWithBackup(database, backup)
|
|
|
|
request := restores_core.RestoreBackupRequest{
|
|
PostgresqlDatabase: &postgresql.PostgresqlDatabase{
|
|
Version: tools.PostgresqlVersion16,
|
|
Host: env_config.GetEnv().TestLocalhost,
|
|
Port: 5432,
|
|
Username: "postgres",
|
|
Password: "postgres",
|
|
CpuCount: 4,
|
|
},
|
|
}
|
|
|
|
testResp := test_utils.MakePostRequest(
|
|
t,
|
|
router,
|
|
fmt.Sprintf("/api/v1/restores/%s/restore", backup.ID.String()),
|
|
"Bearer "+owner.Token,
|
|
request,
|
|
http.StatusOK,
|
|
)
|
|
|
|
assert.Contains(t, string(testResp.Body), "restore started successfully")
|
|
}
|
|
|
|
func cleanupBackup(backup *backups_core.Backup) {
|
|
repo := &backups_core.BackupRepository{}
|
|
repo.DeleteByID(backup.ID)
|
|
}
|
|
|
|
func enableCloud(t *testing.T) {
|
|
t.Helper()
|
|
env_config.GetEnv().IsCloud = true
|
|
t.Cleanup(func() {
|
|
env_config.GetEnv().IsCloud = false
|
|
})
|
|
}
|