From e316124d6e7b5fc70130368e7277e9372039ca6b Mon Sep 17 00:00:00 2001 From: Sam Rose Date: Fri, 3 Nov 2023 11:42:39 +0000 Subject: [PATCH 1/4] Add a test for the user column migration bug fix we did last night. --- .../server/src/api/routes/tests/table.spec.ts | 61 +++++++++++++++++++ .../server/src/tests/utilities/api/row.ts | 21 +++++-- 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/packages/server/src/api/routes/tests/table.spec.ts b/packages/server/src/api/routes/tests/table.spec.ts index c239c596fe..4743bca814 100644 --- a/packages/server/src/api/routes/tests/table.spec.ts +++ b/packages/server/src/api/routes/tests/table.spec.ts @@ -492,6 +492,67 @@ describe("/tables", () => { } }) + it("should succeed when the row is created from the other side of the relationship", async () => { + // We found a bug just after releasing this feature where if the row was created from the + // users table, not the table linking to it, the migration would succeed but lose the data. + // This happened because the order of the documents in the link was reversed. + const table = await config.api.table.create({ + name: "table", + type: "table", + sourceId: INTERNAL_TABLE_SOURCE_ID, + sourceType: TableSourceType.INTERNAL, + schema: { + "user relationship": { + type: FieldType.LINK, + fieldName: "test", + name: "user relationship", + constraints: { + type: "array", + presence: false, + }, + relationshipType: RelationshipType.MANY_TO_ONE, + tableId: InternalTable.USER_METADATA, + }, + }, + }) + + let testRow = await config.api.row.save(table._id!, {}) + + await Promise.all( + users.map(u => + config.api.row.patch(InternalTable.USER_METADATA, { + tableId: InternalTable.USER_METADATA, + _rev: u._rev!, + _id: u._id!, + test: [testRow], + }) + ) + ) + + await config.api.table.migrate(table._id!, { + oldColumn: table.schema["user relationship"], + newColumn: { + name: "user column", + type: FieldType.BB_REFERENCE, + subtype: FieldSubtype.USERS, + }, + }) + + const migratedTable = await config.api.table.get(table._id!) + expect(migratedTable.schema["user column"]).toBeDefined() + expect(migratedTable.schema["user relationship"]).not.toBeDefined() + + const resp = await config.api.row.get(table._id!, testRow._id!) + const migratedRow = resp.body as Row + + expect(migratedRow["user column"]).toBeDefined() + expect(migratedRow["user relationship"]).not.toBeDefined() + expect(migratedRow["user column"]).toHaveLength(3) + expect(migratedRow["user column"].map((u: Row) => u._id)).toEqual( + expect.arrayContaining(users.map(u => u._id)) + ) + }) + it("should successfully migrate a many-to-many user relationship to a users column", async () => { const table = await config.api.table.create({ name: "table", diff --git a/packages/server/src/tests/utilities/api/row.ts b/packages/server/src/tests/utilities/api/row.ts index bb880bb7da..20b1d6f9ee 100644 --- a/packages/server/src/tests/utilities/api/row.ts +++ b/packages/server/src/tests/utilities/api/row.ts @@ -55,7 +55,13 @@ export class RowAPI extends TestAPI { .send(row) .set(this.config.defaultHeaders()) .expect("Content-Type", /json/) - .expect(expectStatus) + if (resp.status !== expectStatus) { + throw new Error( + `Expected status ${expectStatus} but got ${ + resp.status + }, body: ${JSON.stringify(resp.body)}` + ) + } return resp.body as Row } @@ -77,13 +83,20 @@ export class RowAPI extends TestAPI { sourceId: string, row: PatchRowRequest, { expectStatus } = { expectStatus: 200 } - ) => { - return this.request + ): Promise => { + let resp = await this.request .patch(`/api/${sourceId}/rows`) .send(row) .set(this.config.defaultHeaders()) .expect("Content-Type", /json/) - .expect(expectStatus) + if (resp.status !== expectStatus) { + throw new Error( + `Expected status ${expectStatus} but got ${ + resp.status + }, body: ${JSON.stringify(resp.body)}` + ) + } + return resp.body as Row } delete = async ( From 980b9c288299a9f356c0af00067ce57760dbe2e5 Mon Sep 17 00:00:00 2001 From: Sam Rose Date: Fri, 3 Nov 2023 12:53:37 +0000 Subject: [PATCH 2/4] Fix tests. --- .../server/src/api/routes/tests/row.spec.ts | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/packages/server/src/api/routes/tests/row.spec.ts b/packages/server/src/api/routes/tests/row.spec.ts index 3ae4a6c1e2..92d581d930 100644 --- a/packages/server/src/api/routes/tests/row.spec.ts +++ b/packages/server/src/api/routes/tests/row.spec.ts @@ -49,7 +49,12 @@ describe.each([ let table: Table let tableId: string - afterAll(setup.afterAll) + afterAll(async () => { + if (dsProvider) { + await dsProvider.stopContainer() + } + setup.afterAll() + }) beforeAll(async () => { await config.init() @@ -521,20 +526,17 @@ describe.each([ const rowUsage = await getRowUsage() const queryUsage = await getQueryUsage() - const res = await config.api.row.patch(table._id!, { + const row = await config.api.row.patch(table._id!, { _id: existing._id!, _rev: existing._rev!, tableId: table._id!, name: "Updated Name", }) - expect((res as any).res.statusMessage).toEqual( - `${table.name} updated successfully.` - ) - expect(res.body.name).toEqual("Updated Name") - expect(res.body.description).toEqual(existing.description) + expect(row.name).toEqual("Updated Name") + expect(row.description).toEqual(existing.description) - const savedRow = await loadRow(res.body._id, table._id!) + const savedRow = await loadRow(row._id!, table._id!) expect(savedRow.body.description).toEqual(existing.description) expect(savedRow.body.name).toEqual("Updated Name") From a27a9dc2afb2246f64ee1b1333f300290e9b3b5a Mon Sep 17 00:00:00 2001 From: Sam Rose Date: Fri, 3 Nov 2023 14:29:54 +0000 Subject: [PATCH 3/4] Eliminate TOCTOU problem in creating bbTmp. --- packages/backend-core/src/objectStore/utils.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/backend-core/src/objectStore/utils.ts b/packages/backend-core/src/objectStore/utils.ts index dba5f3d1c2..4c3a84ba91 100644 --- a/packages/backend-core/src/objectStore/utils.ts +++ b/packages/backend-core/src/objectStore/utils.ts @@ -18,8 +18,12 @@ export const ObjectStoreBuckets = { } const bbTmp = join(tmpdir(), ".budibase") -if (!fs.existsSync(bbTmp)) { +try { fs.mkdirSync(bbTmp) +} catch (e: any) { + if (e.code !== "EEXIST") { + throw e + } } export function budibaseTempDir() { From 8fed47766aa514936aec4e41c1a64a9d0d3e4fdc Mon Sep 17 00:00:00 2001 From: Budibase Staging Release Bot <> Date: Fri, 3 Nov 2023 15:42:25 +0000 Subject: [PATCH 4/4] Bump version to 2.12.10 --- lerna.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lerna.json b/lerna.json index 2deb06656d..9257a36444 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "2.12.9", + "version": "2.12.10", "npmClient": "yarn", "packages": [ "packages/*"