From bdb1fc1e118cf41151e09c12a7dbd1318e83f4c6 Mon Sep 17 00:00:00 2001 From: Lykke Axlin Date: Sun, 3 Oct 2021 13:01:13 +0200 Subject: [PATCH] added backend test and updated api reports Signed-off-by: Lykke Axlin Co-authored-by: klaraab --- plugins/bazaar-backend/api-report.md | 2 +- plugins/bazaar-backend/package.json | 1 + .../src/service/DatabaseHandler.test.ts | 38 +++++++ .../src/service/DatabaseHandler.ts | 103 ++++++++++++++++++ plugins/bazaar-backend/src/service/router.ts | 35 ++---- plugins/bazaar/api-report.md | 2 +- plugins/bazaar/package.json | 2 +- plugins/bazaar/src/api.ts | 4 +- .../EditProjectDialog/EditProjectDialog.tsx | 2 +- .../EntityBazaarInfoCard.tsx | 58 ++++------ 10 files changed, 177 insertions(+), 70 deletions(-) create mode 100644 plugins/bazaar-backend/src/service/DatabaseHandler.test.ts create mode 100644 plugins/bazaar-backend/src/service/DatabaseHandler.ts diff --git a/plugins/bazaar-backend/api-report.md b/plugins/bazaar-backend/api-report.md index fee1bf92fd..89f6da5305 100644 --- a/plugins/bazaar-backend/api-report.md +++ b/plugins/bazaar-backend/api-report.md @@ -20,7 +20,7 @@ export interface RouterOptions { // (undocumented) config: Config; // (undocumented) - database?: PluginDatabaseManager; + database: PluginDatabaseManager; // (undocumented) logger: Logger_2; } diff --git a/plugins/bazaar-backend/package.json b/plugins/bazaar-backend/package.json index 11af15e6f8..8d03e8a128 100644 --- a/plugins/bazaar-backend/package.json +++ b/plugins/bazaar-backend/package.json @@ -27,6 +27,7 @@ "express-promise-router": "^4.1.0", "knex": "^0.95.1", "supertest": "^6.1.6", + "uuid": "^8.3.2", "winston": "^3.2.1", "yn": "^4.0.0" }, diff --git a/plugins/bazaar-backend/src/service/DatabaseHandler.test.ts b/plugins/bazaar-backend/src/service/DatabaseHandler.test.ts new file mode 100644 index 0000000000..0ed40b438a --- /dev/null +++ b/plugins/bazaar-backend/src/service/DatabaseHandler.test.ts @@ -0,0 +1,38 @@ +/* + * Copyright 2021 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 { DatabaseHandler } from './DatabaseHandler'; + +const members: Array = [ + { + entity_ref: 'project1', + user_id: 'member1', + }, +]; + +describe('DatabaseHandler', () => { + let database: DatabaseHandler; + beforeAll(async () => { + database = await DatabaseHandler.createTestDatabase(); + await database.addMember(members[0].user_id, members[0].entity_ref); + }); + + it("can get members that's in the database", async () => { + const cov: any[] = await database.getMembers('project1'); + expect(cov[0].entity_ref).toEqual(members[0].entity_ref); + expect(cov[0].user_id).toEqual(members[0].user_id); + }); +}); diff --git a/plugins/bazaar-backend/src/service/DatabaseHandler.ts b/plugins/bazaar-backend/src/service/DatabaseHandler.ts new file mode 100644 index 0000000000..7b1eab723f --- /dev/null +++ b/plugins/bazaar-backend/src/service/DatabaseHandler.ts @@ -0,0 +1,103 @@ +/* + * Copyright 2021 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 { resolvePackagePath } from '@backstage/backend-common'; +import knexFactory, { Knex } from 'knex'; +import { v4 as uuidv4 } from 'uuid'; + +const migrationsDir = resolvePackagePath( + '@backstage/plugin-bazaar-backend', + 'migrations', +); + +type Options = { + database: Knex; +}; + +export class DatabaseHandler { + static async create(options: Options): Promise { + const { database } = options; + + await database.migrate.latest({ + directory: migrationsDir, + }); + + return new DatabaseHandler(options); + } + + private readonly database: Knex; + + private constructor(options: Options) { + this.database = options.database; + } + + public static async createTestDatabase(): Promise { + const knex = await this.createTestDatabaseConnection(); + return await this.create({ database: knex }); + } + + public static async createTestDatabaseConnection(): Promise { + const config: Knex.Config = { + client: 'pg', + connection: { + host: 'localhost', + port: 5432, + user: 'postgres', + password: 'postgres', + }, + /* + client: 'sqlite3', + connection: ':memory:', + useNullAsDefault: true, + */ + }; + + let knex = knexFactory(config); + if (typeof config.connection !== 'string') { + const tempDbName = `d${uuidv4().replace(/-/g, '')}`; + await knex.raw(`CREATE DATABASE ${tempDbName};`); + knex = knexFactory({ + ...config, + connection: { + ...config.connection, + database: tempDbName, + }, + }); + } + + knex.client.pool.on('createSuccess', (_eventId: any, resource: any) => { + resource.run('PRAGMA foreign_keys = ON', () => {}); + }); + + return knex; + } + + async getMembers(entityRef: any) { + return await this.database + .select('*') + .from('public.members') + .where({ entity_ref: entityRef }); + } + + async addMember(userId: any, entityRef: any) { + await this.database + .insert({ + entity_ref: entityRef, + user_id: userId, + }) + .into('public.members'); + } +} diff --git a/plugins/bazaar-backend/src/service/router.ts b/plugins/bazaar-backend/src/service/router.ts index 74161a8acf..26c1c2bda8 100644 --- a/plugins/bazaar-backend/src/service/router.ts +++ b/plugins/bazaar-backend/src/service/router.ts @@ -14,15 +14,12 @@ * limitations under the License. */ -import { - errorHandler, - PluginDatabaseManager, - resolvePackagePath, -} from '@backstage/backend-common'; +import { errorHandler, PluginDatabaseManager } from '@backstage/backend-common'; import express from 'express'; import Router from 'express-promise-router'; import { Logger } from 'winston'; import { Config } from '@backstage/config'; +import { DatabaseHandler } from './DatabaseHandler'; export interface RouterOptions { logger: Logger; @@ -33,30 +30,20 @@ export interface RouterOptions { export async function createRouter( options: RouterOptions, ): Promise { - const { logger } = options; - const db = await options.database.getClient(); + const { logger, database } = options; + const db = await database.getClient(); + + const dbHandler = await DatabaseHandler.create({ database: db }); logger.info('Initializing Bazaar backend'); - const migrationsDir = resolvePackagePath( - '@backstage/plugin-bazaar-backend', - 'migrations', - ); - - await db.migrate.latest({ - directory: migrationsDir, - }); - const router = Router(); router.use(express.json()); router.get('/members', async (request, response) => { const entityRef = request.headers.entity_ref; - const data = await db - .select('*') - .from('public.members') - .where({ entity_ref: entityRef }); + const data = await dbHandler.getMembers(entityRef); if (data?.length) { response.json({ status: 'ok', data: data }); @@ -69,12 +56,8 @@ export async function createRouter( const userId = request.headers.user_id; const entityRef = request.headers.entity_ref; - await db - .insert({ - entity_ref: entityRef, - user_id: userId, - }) - .into('public.members'); + await dbHandler.addMember(userId, entityRef); + response.json({ status: 'ok' }); }); diff --git a/plugins/bazaar/api-report.md b/plugins/bazaar/api-report.md index 46d761e5ba..de94d7a48e 100644 --- a/plugins/bazaar/api-report.md +++ b/plugins/bazaar/api-report.md @@ -26,7 +26,7 @@ export const bazaarPlugin: BackstagePlugin< // Warning: (ae-missing-release-tag) "EntityBazaarInfoCard" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export const EntityBazaarInfoCard: () => JSX.Element; +export const EntityBazaarInfoCard: () => JSX.Element | null; // (No @packageDocumentation comment for this package) ``` diff --git a/plugins/bazaar/package.json b/plugins/bazaar/package.json index c0d0ecefe4..066ba04419 100644 --- a/plugins/bazaar/package.json +++ b/plugins/bazaar/package.json @@ -26,7 +26,7 @@ "@backstage/core-components": "^0.5.0", "@backstage/core-plugin-api": "^0.1.3", "@backstage/plugin-catalog": "^0.6.6", - "@backstage/plugin-catalog-react": "^0.4.0", + "@backstage/plugin-catalog-react": "^0.5.0", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", diff --git a/plugins/bazaar/src/api.ts b/plugins/bazaar/src/api.ts index 5cdbfb8928..9a1666e71d 100644 --- a/plugins/bazaar/src/api.ts +++ b/plugins/bazaar/src/api.ts @@ -91,12 +91,14 @@ export class BazaarClient implements BazaarApi { async getMetadata(entity: Entity): Promise { const baseUrl = await this.discoveryApi.getBaseUrl('bazaar'); - return await fetch(`${baseUrl}/metadata`, { + const response = await fetch(`${baseUrl}/metadata`, { method: 'GET', headers: { entity_ref: stringifyEntityRef(entity), }, }); + + return response.ok ? response : null; } async getMembers(entity: Entity): Promise { diff --git a/plugins/bazaar/src/components/EditProjectDialog/EditProjectDialog.tsx b/plugins/bazaar/src/components/EditProjectDialog/EditProjectDialog.tsx index 82b6da492f..03d54136cc 100644 --- a/plugins/bazaar/src/components/EditProjectDialog/EditProjectDialog.tsx +++ b/plugins/bazaar/src/components/EditProjectDialog/EditProjectDialog.tsx @@ -24,7 +24,7 @@ import { bazaarApiRef } from '../../api'; type Props = { entity: Entity; bazaarProject: BazaarProject; - fetchBazaarProject: () => Promise; + fetchBazaarProject: () => Promise; open: boolean; handleClose: () => void; isAddForm: boolean; diff --git a/plugins/bazaar/src/components/EntityBazaarInfoCard/EntityBazaarInfoCard.tsx b/plugins/bazaar/src/components/EntityBazaarInfoCard/EntityBazaarInfoCard.tsx index 2b6b8c3a41..0353b2c3ae 100644 --- a/plugins/bazaar/src/components/EntityBazaarInfoCard/EntityBazaarInfoCard.tsx +++ b/plugins/bazaar/src/components/EntityBazaarInfoCard/EntityBazaarInfoCard.tsx @@ -47,14 +47,9 @@ import DeleteIcon from '@material-ui/icons/Delete'; import { EditProjectDialog } from '../EditProjectDialog'; import { DeleteProjectDialog } from '../DeleteProjectDialog'; import ExitToAppIcon from '@material-ui/icons/ExitToApp'; -import { - useApi, - identityApiRef, - useRouteRef, -} from '@backstage/core-plugin-api'; +import { useApi, identityApiRef } from '@backstage/core-plugin-api'; import { Member, BazaarProject } from '../../types'; import { bazaarApiRef } from '../../api'; -import { rootRouteRef } from '../../routes'; import { Alert } from '@material-ui/lab'; import { useAsyncFn } from 'react-use'; @@ -92,7 +87,6 @@ export const EntityBazaarInfoCard = () => { const [openDelete, setOpenDelete] = useState(false); const [isMember, setIsMember] = useState(false); const [isBazaar, setIsBazaar] = useState(false); - const routeRef = useRouteRef(rootRouteRef); const [members, fetchMembers] = useAsyncFn(async () => { const response = await bazaarApi.getMembers(entity); const dbMembers = response.data.map((obj: any) => { @@ -112,17 +106,21 @@ export const EntityBazaarInfoCard = () => { const [bazaarProject, fetchBazaarProject] = useAsyncFn(async () => { const response = await bazaarApi.getMetadata(entity); - const metadata = await response.json().then((resp: any) => resp.data[0]); - return { - entityRef: metadata.entity_ref, - name: metadata.name, - community: metadata.community, - announcement: metadata.announcement, - status: metadata.status, - updatedAt: metadata.updated_at, - membersCount: metadata.members_count, - } as BazaarProject; + if (response) { + const metadata = await response.json().then((resp: any) => resp.data[0]); + + return { + entityRef: metadata.entity_ref, + name: metadata.name, + community: metadata.community, + announcement: metadata.announcement, + status: metadata.status, + updatedAt: metadata.updated_at, + membersCount: metadata.members_count, + } as BazaarProject; + } + return null; }); useEffect(() => { @@ -135,7 +133,7 @@ export const EntityBazaarInfoCard = () => { members?.value ?.map((member: Member) => member.userId) .indexOf(identity.getUserId()) >= 0; - const isBazaarProject = bazaarProject !== undefined; + const isBazaarProject = bazaarProject !== null; setIsMember(isBazaarMember); setIsBazaar(isBazaarProject); @@ -185,32 +183,14 @@ export const EntityBazaarInfoCard = () => { }, ]; - if (bazaarProject.loading || members.loading) { + if (!isBazaar) { + return null; + } else if (bazaarProject.loading || members.loading) { return ; } else if (bazaarProject.error) { return {bazaarProject?.error?.message}; } else if (members.error) { return {members?.error?.message}; - } else if (!isBazaar) { - return ( - - - - - - This project is not in the Bazaar. Go to the{' '} - - Bazaar - {' '} - to add the project or to{' '} - - read more - - . - - - - ); } return (