From 16b2f7a3eb45796705f82538e96389e436955697 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sat, 13 Apr 2024 13:14:30 +0200 Subject: [PATCH] app-backend: added migration test + fix namespace migration Signed-off-by: Patrik Oldsberg --- .../20240113144027_assets-namespace.js | 8 +- .../src/lib/assets/StaticAssetsStore.ts | 2 +- plugins/app-backend/src/migrations.test.ts | 141 ++++++++++++++++++ 3 files changed, 148 insertions(+), 3 deletions(-) create mode 100644 plugins/app-backend/src/migrations.test.ts diff --git a/plugins/app-backend/migrations/20240113144027_assets-namespace.js b/plugins/app-backend/migrations/20240113144027_assets-namespace.js index b533d35dfa..c39407d08b 100644 --- a/plugins/app-backend/migrations/20240113144027_assets-namespace.js +++ b/plugins/app-backend/migrations/20240113144027_assets-namespace.js @@ -23,7 +23,8 @@ exports.up = async function up(knex) { await knex.schema.alterTable('static_assets_cache', table => { // The namespace is used to allow operations on asset groups, e.g. delete all assets in a namespace table.string('namespace').comment('The namespace of the file'); - table.index('namespace', 'static_asset_cache_namespace_idx'); + table.dropPrimary(); + table.primary(['namespace', 'path']); }); }; @@ -31,8 +32,11 @@ exports.up = async function up(knex) { * @param {import('knex').Knex} knex */ exports.down = async function down(knex) { + await knex.delete().from('static_assets_cache').whereNotNull('namespace'); + await knex.schema.alterTable('static_assets_cache', table => { - table.dropIndex([], 'static_asset_cache_namespace_idx'); + table.dropPrimary(); table.dropColumn('namespace'); + table.primary(['path']); }); }; diff --git a/plugins/app-backend/src/lib/assets/StaticAssetsStore.ts b/plugins/app-backend/src/lib/assets/StaticAssetsStore.ts index 43bd38030f..27573300e7 100644 --- a/plugins/app-backend/src/lib/assets/StaticAssetsStore.ts +++ b/plugins/app-backend/src/lib/assets/StaticAssetsStore.ts @@ -120,7 +120,7 @@ export class StaticAssetsStore implements StaticAssetProvider { content: await asset.content(), namespace: this.#namespace, }) - .onConflict('path') + .onConflict(['namespace', 'path']) .ignore(); } } diff --git a/plugins/app-backend/src/migrations.test.ts b/plugins/app-backend/src/migrations.test.ts new file mode 100644 index 0000000000..8a413559d2 --- /dev/null +++ b/plugins/app-backend/src/migrations.test.ts @@ -0,0 +1,141 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Knex } from 'knex'; +import { TestDatabases } from '@backstage/backend-test-utils'; +import fs from 'fs'; + +const migrationsDir = `${__dirname}/../migrations`; +const migrationsFiles = fs.readdirSync(migrationsDir).sort(); + +async function migrateUpOnce(knex: Knex): Promise { + await knex.migrate.up({ directory: migrationsDir }); +} + +async function migrateDownOnce(knex: Knex): Promise { + await knex.migrate.down({ directory: migrationsDir }); +} + +async function migrateUntilBefore(knex: Knex, target: string): Promise { + const index = migrationsFiles.indexOf(target); + if (index === -1) { + throw new Error(`Migration ${target} not found`); + } + for (let i = 0; i < index; i++) { + await migrateUpOnce(knex); + } +} + +jest.setTimeout(60_000); + +describe('migrations', () => { + const databases = TestDatabases.create(); + + it.each(databases.eachSupportedId())( + '20211229105307_init.js, %p', + async databaseId => { + const knex = await databases.init(databaseId); + + await migrateUntilBefore(knex, '20211229105307_init.js'); + await migrateUpOnce(knex); + + await knex('static_assets_cache').insert({ + path: 'main.js', + content: Buffer.from('some-script'), + last_modified_at: knex.fn.now(), + }); + + await expect(knex('static_assets_cache')).resolves.toEqual([ + { + path: 'main.js', + content: Buffer.from('some-script'), + last_modified_at: expect.anything(), + }, + ]); + + await migrateDownOnce(knex); + + // This looks odd - you might expect a .toThrow at the end but that + // actually is flaky for some reason specifically on sqlite when + // performing multiple runs in sequence + await expect(knex('static_assets_cache')).rejects.toEqual( + expect.anything(), + ); + + await knex.destroy(); + }, + ); + + it.each(databases.eachSupportedId())( + '20240113144027_assets-namespace.js, %p', + async databaseId => { + const knex = await databases.init(databaseId); + + await migrateUntilBefore(knex, '20240113144027_assets-namespace.js'); + + await knex('static_assets_cache').insert({ + path: 'main.js', + content: Buffer.from('some-script'), + last_modified_at: knex.fn.now(), + }); + + await migrateUpOnce(knex); + + await expect(knex('static_assets_cache')).resolves.toEqual([ + { + path: 'main.js', + content: Buffer.from('some-script'), + namespace: 'default', + last_modified_at: expect.anything(), + }, + ]); + + await knex('static_assets_cache').insert({ + path: 'main.js', + content: Buffer.from('other-script'), + namespace: 'other', + last_modified_at: knex.fn.now(), + }); + + await expect(knex('static_assets_cache')).resolves.toEqual([ + { + path: 'main.js', + content: Buffer.from('some-script'), + namespace: 'default', + last_modified_at: expect.anything(), + }, + { + path: 'main.js', + content: Buffer.from('other-script'), + namespace: 'other', + last_modified_at: expect.anything(), + }, + ]); + + await migrateDownOnce(knex); + + await expect(knex('static_assets_cache')).resolves.toEqual([ + { + path: 'main.js', + content: Buffer.from('some-script'), + last_modified_at: expect.anything(), + }, + ]); + + await knex.destroy(); + }, + ); +});