@budibase/server
Version:
Budibase Web Server
225 lines (183 loc) • 6.56 kB
text/typescript
import tk from "timekeeper"
import * as setup from "../../../api/routes/tests/utilities"
import { processMigrations } from "../../migrationsProcessor"
import migration from "../20250729134531_workspace_cleanups"
import { AppMigration, updateAppMigrationMetadata } from "../.."
import sdk from "../../../sdk"
import { structures } from "@budibase/backend-core/tests"
import { db } from "@budibase/backend-core"
import { basicScreen } from "../../../tests/utilities/structures"
import { WorkspaceApp } from "@budibase/types"
const MIGRATIONS: AppMigration[] = [
{
id: "20250729134531_workspace_cleanups",
func: jest.fn().mockImplementation(migration),
disabled: false,
},
]
const config = setup.getConfig()
async function createWorkspaceApp(isDefault = false) {
const workspaceApp = await sdk.workspaceApps.create(
structures.workspaceApps.workspaceApp({ isDefault })
)
return workspaceApp
}
describe.each([
["dev", () => config.getAppId()],
["prod", () => config.getProdAppId()],
])("Workspace cleanups migration (%s)", (_, getAppId) => {
let defaultWorkspaceApp: WorkspaceApp
beforeAll(async () => {
await config.init()
})
beforeEach(async () => {
await config.newTenant()
for (const appId of [config.getAppId(), config.getProdAppId()]) {
await config.doInContext(appId, async () => {
await updateAppMigrationMetadata({
appId,
version: "",
})
})
const workspaceApps = await config.doInContext(
getAppId(),
sdk.workspaceApps.fetch
)
expect(workspaceApps).toHaveLength(1)
defaultWorkspaceApp = workspaceApps[0]
}
tk.freeze(structures.generator.date())
jest.clearAllMocks()
})
afterEach(() => {
expect(MIGRATIONS[0].func).toHaveBeenCalledTimes(2)
})
it("does nothing when only one default workspace app exists", async () => {
await config.doInContext(getAppId(), () =>
processMigrations(config.getAppId(), MIGRATIONS)
)
const workspaceApps = await config.doInContext(
getAppId(),
sdk.workspaceApps.fetch
)
expect(workspaceApps).toHaveLength(1)
expect(workspaceApps[0].isDefault).toBe(true)
})
it("does nothing when multiple non-default workspace apps exist", async () => {
await config.doInContext(getAppId(), async () => {
await createWorkspaceApp(false)
await createWorkspaceApp(false)
})
await config.doInContext(getAppId(), () =>
processMigrations(config.getAppId(), MIGRATIONS)
)
const workspaceApps = await config.doInContext(
getAppId(),
sdk.workspaceApps.fetch
)
expect(workspaceApps).toHaveLength(3)
})
it("removes duplicate default workspace apps and keeps only one", async () => {
await config.doInContext(getAppId(), async () => {
await createWorkspaceApp(true)
await createWorkspaceApp(true)
await createWorkspaceApp(true)
})
await config.doInContext(getAppId(), () =>
processMigrations(config.getAppId(), MIGRATIONS)
)
const workspaceApps = await config.doInContext(
getAppId(),
sdk.workspaceApps.fetch
)
expect(workspaceApps).toHaveLength(1)
expect(workspaceApps[0].isDefault).toBe(true)
})
it("updates screens to reference the kept workspace app when removing duplicates", async () => {
let keptWorkspaceAppId: string = undefined!
await config.doInContext(getAppId(), async () => {
const workspaceApp1 = await createWorkspaceApp(true)
const workspaceApp2 = await createWorkspaceApp(true)
tk.travel(Date.now() + 1000)
await sdk.workspaceApps.update(workspaceApp1)
const toDelete1 = defaultWorkspaceApp._id!
const toDelete2 = workspaceApp2._id!
keptWorkspaceAppId = workspaceApp1._id!
// Create screens that reference both workspace apps
await sdk.screens.create({
...basicScreen(),
workspaceAppId: toDelete1,
})
await sdk.screens.create({
...basicScreen(),
workspaceAppId: toDelete2,
})
await sdk.screens.create({
...basicScreen(),
workspaceAppId: toDelete1,
})
})
await config.doInContext(getAppId(), () =>
processMigrations(config.getAppId(), MIGRATIONS)
)
const workspaceApps = await config.doInContext(
getAppId(),
sdk.workspaceApps.fetch
)
const screens = await config.doInContext(getAppId(), sdk.screens.fetch)
expect(workspaceApps).toHaveLength(1)
expect(workspaceApps[0]._id).toBe(keptWorkspaceAppId)
// All screens should now reference the kept workspace app
const screensWithWorkspaceAppId = screens.filter(s => s.workspaceAppId)
expect(screensWithWorkspaceAppId).toHaveLength(4)
screensWithWorkspaceAppId.forEach(screen => {
expect(screen.workspaceAppId).toBe(keptWorkspaceAppId)
})
})
it("handles mixed default and non-default workspace apps correctly", async () => {
await config.doInContext(getAppId(), async () => {
await createWorkspaceApp(true)
await createWorkspaceApp(true)
await createWorkspaceApp(false)
})
await config.doInContext(getAppId(), () =>
processMigrations(config.getAppId(), MIGRATIONS)
)
const workspaceApps = await config.doInContext(
getAppId(),
sdk.workspaceApps.fetch
)
expect(workspaceApps).toHaveLength(2)
const defaultApps = workspaceApps.filter(app => app.isDefault)
const nonDefaultApps = workspaceApps.filter(app => !app.isDefault)
expect(defaultApps).toHaveLength(1)
expect(nonDefaultApps).toHaveLength(1)
})
it("handles different workspace apps between dev and prod", async () => {
const prodAppId = db.getProdAppID(getAppId())
const devAppId = db.getDevAppID(getAppId())
await config.doInContext(prodAppId, async () => {
await createWorkspaceApp(true)
})
await config.doInContext(devAppId, async () => {
await createWorkspaceApp(true)
})
expect(
await config.doInContext(prodAppId, async () =>
(await sdk.workspaceApps.fetch()).map(a => a._id)
)
).not.toEqual(
await config.doInContext(devAppId, async () =>
(await sdk.workspaceApps.fetch()).map(a => a._id)
)
)
await config.doInContext(getAppId(), () =>
processMigrations(config.getAppId(), MIGRATIONS)
)
const workspaceApps = await config.doInContext(
getAppId(),
sdk.workspaceApps.fetch
)
expect(workspaceApps).toHaveLength(1)
})
})