From d9dda535b158cd20ee7fdbe7a14a6c5ac7cf5249 Mon Sep 17 00:00:00 2001 From: golobitch Date: Tue, 3 Sep 2024 22:19:32 +0200 Subject: [PATCH] feat(backend): formating + add basic tenant query resolvers test --- packages/backend/jest.config.js | 2 + .../20240902220115_add_tenant_name.js | 32 +++-- .../backend/src/graphql/resolvers/index.ts | 1 - .../src/graphql/resolvers/tenant.test.ts | 115 ++++++++++++++++++ .../backend/src/tenant/endpoints/model.ts | 2 +- .../backend/src/tenant/endpoints/service.ts | 14 ++- packages/backend/src/tenant/service.ts | 12 +- packages/backend/src/tests/app.ts | 13 +- packages/backend/src/tests/tenant.ts | 46 +++++++ 9 files changed, 197 insertions(+), 40 deletions(-) create mode 100644 packages/backend/src/graphql/resolvers/tenant.test.ts create mode 100644 packages/backend/src/tests/tenant.ts diff --git a/packages/backend/jest.config.js b/packages/backend/jest.config.js index e6527265b5..61d32249a3 100644 --- a/packages/backend/jest.config.js +++ b/packages/backend/jest.config.js @@ -18,6 +18,8 @@ process.env.USE_TIGERBEETLE = false process.env.ENABLE_TELEMETRY = false process.env.KRATOS_ADMIN_URL = 'http://127.0.0.1:4434/admin' process.env.KRATOS_ADMIN_EMAIL = 'admin@mail.com' +process.env.AUTH_ADMIN_URL = 'http://example.com/graphql' +process.env.AUTH_ADMIN_API_SECRET = 'verysecuresecret' module.exports = { ...baseConfig, diff --git a/packages/backend/migrations/20240902220115_add_tenant_name.js b/packages/backend/migrations/20240902220115_add_tenant_name.js index 23618281bc..dd9ac5c59c 100644 --- a/packages/backend/migrations/20240902220115_add_tenant_name.js +++ b/packages/backend/migrations/20240902220115_add_tenant_name.js @@ -3,21 +3,17 @@ * @returns { Promise } */ exports.up = function (knex) { - return knex.schema - .table('tenants', function (table) { - table.string('name').unique().notNullable() - }) - } - - /** - * @param { import("knex").Knex } knex - * @returns { Promise } - */ - exports.down = function (knex) { - return knex.schema - .table('tenants', function (table) { - table.dropColumn('name') - }) - - } - \ No newline at end of file + return knex.schema.table('tenants', function (table) { + table.string('name').unique().notNullable() + }) +} + +/** + * @param { import("knex").Knex } knex + * @returns { Promise } + */ +exports.down = function (knex) { + return knex.schema.table('tenants', function (table) { + table.dropColumn('name') + }) +} diff --git a/packages/backend/src/graphql/resolvers/index.ts b/packages/backend/src/graphql/resolvers/index.ts index e6f05f04ae..fa27b2224b 100644 --- a/packages/backend/src/graphql/resolvers/index.ts +++ b/packages/backend/src/graphql/resolvers/index.ts @@ -68,7 +68,6 @@ import { getCombinedPayments } from './combined_payments' import { createOrUpdatePeerByUrl } from './auto-peering' import { getAccountingTransfers } from './accounting_transfer' import { createTenant, getTenant, getTenants } from './tenant' -import { getTenantEndpoints } from './tenant_endpoints' export const resolvers: Resolvers = { UInt8: GraphQLUInt8, diff --git a/packages/backend/src/graphql/resolvers/tenant.test.ts b/packages/backend/src/graphql/resolvers/tenant.test.ts new file mode 100644 index 0000000000..671053df90 --- /dev/null +++ b/packages/backend/src/graphql/resolvers/tenant.test.ts @@ -0,0 +1,115 @@ +import { Knex } from 'knex' +import { IocContract } from '@adonisjs/fold' +import { createTestApp, TestContainer } from '../../tests/app' +import { AppServices } from '../../app' +import { initIocContainer } from '../..' +import { Config, IAppConfig } from '../../config/app' +import { truncateTables } from '../../tests/tableManager' +import { getPageTests } from './page.test' +import { + createTenant, + mockAdminAuthApiTenantCreation +} from '../../tests/tenant' +import { ApolloError, gql } from '@apollo/client' +import { Scope } from 'nock' +import { v4 as uuidv4 } from 'uuid' +import { errorToCode, errorToMessage, TenantError } from '../../tenant/errors' + +describe('Tenant Resolver', (): void => { + let deps: IocContract + let appContainer: TestContainer + let knex: Knex + let config: IAppConfig + let scope: Scope + + beforeAll(async (): Promise => { + deps = initIocContainer(Config) + appContainer = await createTestApp(deps) + knex = appContainer.knex + config = await deps.use('config') + scope = mockAdminAuthApiTenantCreation(config.authAdminApiUrl).persist() + }) + + afterEach(async (): Promise => { + await truncateTables(knex) + }) + + afterAll(async (): Promise => { + scope.done() + appContainer.apolloClient.stop() + await appContainer.shutdown() + }) + + describe('Tenant Queries', (): void => { + getPageTests({ + getClient: () => appContainer.apolloClient, + createModel: async () => createTenant(deps), + pagedQuery: 'tenants' + }) + + test('should return error if tenant does not exists', async (): Promise => { + try { + await appContainer.apolloClient + .query({ + query: gql` + query GetTenant($id: ID!) { + tenant(id: $id) { + id + name + kratosIdentityId + } + } + `, + variables: { + id: uuidv4() + } + }) + .then((query) => { + if (query.data) return query.data.tenant + throw new Error('Data was empty') + }) + } catch (error) { + expect(error).toBeInstanceOf(ApolloError) + expect((error as ApolloError).graphQLErrors).toContainEqual( + expect.objectContaining({ + message: errorToMessage[TenantError.UnknownTenant], + extensions: expect.objectContaining({ + code: errorToCode[TenantError.UnknownTenant] + }) + }) + ) + } + }) + + test('should get correct tenant', async (): Promise => { + const tenant = await createTenant(deps) + + const response = await appContainer.apolloClient + .query({ + query: gql` + query GetTenant($id: ID!) { + tenant(id: $id) { + id + name + kratosIdentityId + } + } + `, + variables: { + id: tenant.id + } + }) + .then((query) => { + if (query.data) return query.data.tenant + throw new Error('Data was empty') + }) + + expect(response).toEqual({ + __typename: 'Tenant', + id: tenant.id, + name: tenant.name, + kratosIdentityId: tenant.kratosIdentityId + }) + }) + }) +}) diff --git a/packages/backend/src/tenant/endpoints/model.ts b/packages/backend/src/tenant/endpoints/model.ts index b0b16c7406..50e3b568ac 100644 --- a/packages/backend/src/tenant/endpoints/model.ts +++ b/packages/backend/src/tenant/endpoints/model.ts @@ -13,7 +13,7 @@ export class TenantEndpoint extends WeakModel { // Tell Objection.js that there is no single id column // Define the composite primary key static get idColumn() { - return ['tenantId', 'type']; + return ['tenantId', 'type'] } public type!: EndpointType diff --git a/packages/backend/src/tenant/endpoints/service.ts b/packages/backend/src/tenant/endpoints/service.ts index 39ae2e0732..58e02ad4f0 100644 --- a/packages/backend/src/tenant/endpoints/service.ts +++ b/packages/backend/src/tenant/endpoints/service.ts @@ -58,10 +58,11 @@ async function getTenantEndpointsPage( pagination?: Pagination, sortOrder?: SortOrder ) { - const data = await TenantEndpoint - .query(deps.knex) - .getPage(pagination, sortOrder) - return data + const data = await TenantEndpoint.query(deps.knex).getPage( + pagination, + sortOrder + ) + return data } async function createTenantEndpoint( @@ -74,8 +75,9 @@ async function createTenantEndpoint( tenantId: createOptions.tenantId })) - return await TenantEndpoint.query(createOptions.trx) - .insert(tenantEndpointsData) + return await TenantEndpoint.query(createOptions.trx).insert( + tenantEndpointsData + ) } async function getEndpointsForTenant( diff --git a/packages/backend/src/tenant/service.ts b/packages/backend/src/tenant/service.ts index 691326bd0c..7827329d25 100644 --- a/packages/backend/src/tenant/service.ts +++ b/packages/backend/src/tenant/service.ts @@ -11,8 +11,6 @@ import { import { v4 as uuidv4 } from 'uuid' import { Pagination, SortOrder } from '../shared/baseModel' import { EndpointOptions, TenantEndpointService } from './endpoints/service' -import { isTenantEndpointError } from './endpoints/errors' -import { tr } from '@faker-js/faker' export interface CreateTenantOptions { name: string @@ -61,6 +59,7 @@ async function getTenantsPage( sortOrder?: SortOrder ): Promise { return await Tenant.query(deps.knex) + .withGraphFetched('endpoints') .getPage(pagination, sortOrder) } @@ -68,9 +67,7 @@ async function getTenant( deps: ServiceDependencies, id: string ): Promise { - return Tenant.query(deps.knex) - .withGraphFetched('endpoints') - .findById(id) + return Tenant.query(deps.knex).withGraphFetched('endpoints').findById(id) } async function createTenant( @@ -92,9 +89,8 @@ async function createTenant( kratosIdentityId: uuidv4(), endpoints: options.endpoints } - - tenant = await Tenant.query(trx) - .insertGraphAndFetch(tenantData) + + tenant = await Tenant.query(trx).insertGraphAndFetch(tenantData) // call auth admin api const mutation = gql` diff --git a/packages/backend/src/tests/app.ts b/packages/backend/src/tests/app.ts index cbe82b4704..4acc3dc5ed 100644 --- a/packages/backend/src/tests/app.ts +++ b/packages/backend/src/tests/app.ts @@ -1,19 +1,18 @@ import Axios from 'axios' import { Knex } from 'knex' -import fetch from 'cross-fetch' import { IocContract } from '@adonisjs/fold' import { ApolloClient, ApolloLink, + createHttpLink, InMemoryCache, - NormalizedCacheObject, - createHttpLink + NormalizedCacheObject } from '@apollo/client' -import { setContext } from '@apollo/client/link/context' import { start, gracefulShutdown } from '..' -import { onError } from '@apollo/client/link/error' import { App, AppServices } from '../app' +import { setContext } from '@apollo/client/link/context' +import { onError } from '@apollo/client/link/error' export const testAccessToken = 'test-app-access' @@ -23,6 +22,7 @@ export interface TestContainer { app: App knex: Knex apolloClient: ApolloClient + authApolloClient: ApolloClient connectionUrl: string shutdown: () => Promise container: IocContract @@ -38,7 +38,6 @@ export const createTestApp = async ( config.autoPeeringServerPort = 0 config.openPaymentsUrl = 'https://op.example' config.walletAddressUrl = 'https://wallet.example/.well-known/pay' - const logger = await container.use('logger') const app = new App(container) await start(container, app) @@ -57,6 +56,7 @@ export const createTestApp = async ( .persist() const knex = await container.use('knex') + const logger = await container.use('logger') const httpLink = createHttpLink({ uri: `http://localhost:${app.getAdminPort()}/graphql`, @@ -108,6 +108,7 @@ export const createTestApp = async ( openPaymentsPort: app.getOpenPaymentsPort(), knex, apolloClient: client, + authApolloClient: await container.use('apolloClient'), connectionUrl: config.databaseUrl, shutdown: async () => { nock.cleanAll() diff --git a/packages/backend/src/tests/tenant.ts b/packages/backend/src/tests/tenant.ts new file mode 100644 index 0000000000..bb90953f09 --- /dev/null +++ b/packages/backend/src/tests/tenant.ts @@ -0,0 +1,46 @@ +import { IocContract } from '@adonisjs/fold' +import { AppServices } from '../app' +import { Tenant } from '../tenant/model' +import { CreateTenantOptions } from '../tenant/service' +import { v4 as uuidv4 } from 'uuid' +import { EndpointType } from '../tenant/endpoints/model' +import nock from 'nock' + +export function mockAdminAuthApiTenantCreation(mockedUrl: string) { + const url = new URL(mockedUrl) + return nock(`${url.protocol}//${url.host}`) + .post(/.*/) + .reply(200, { + data: { + createTenant: { + tenant: { + id: uuidv4() + } + } + } + }) +} + +export async function createTenant( + deps: IocContract +): Promise { + const tenantService = await deps.use('tenantService') + + const options: CreateTenantOptions = { + name: uuidv4(), + idpConsentEndpoint: `https://example.com/${uuidv4()}`, + idpSecret: `secret-${uuidv4()}`, + endpoints: [ + { + type: EndpointType.RatesUrl, + value: `https://example.com/rates/${uuidv4()}` + }, + { + type: EndpointType.WebhookBaseUrl, + value: `https://example.com/webhook/${uuidv4()}` + } + ] + } + + return tenantService.create(options) as Promise +}