From 563563193ef9a576670fc9595cd4a99c56ae93c6 Mon Sep 17 00:00:00 2001 From: Blair Currey <12960453+BlairCurrey@users.noreply.github.com> Date: Wed, 18 Dec 2024 14:28:31 -0500 Subject: [PATCH] feat(backend): use auth service client in tenant service --- .../backend/src/auth-service-client/client.ts | 10 +- packages/backend/src/index.ts | 6 +- packages/backend/src/tenants/service.test.ts | 204 +++++++----------- packages/backend/src/tenants/service.ts | 64 ++---- packages/backend/src/tests/tenant.ts | 10 +- 5 files changed, 100 insertions(+), 194 deletions(-) diff --git a/packages/backend/src/auth-service-client/client.ts b/packages/backend/src/auth-service-client/client.ts index 7362164cd4..5dcfac2c91 100644 --- a/packages/backend/src/auth-service-client/client.ts +++ b/packages/backend/src/auth-service-client/client.ts @@ -25,10 +25,7 @@ export class AuthServiceClient { } // eslint-disable-next-line @typescript-eslint/no-explicit-any - private async request( - path: string, - options: RequestInit - ): Promise { + private async request(path: string, options: RequestInit): Promise { const response = await fetch(`${this.baseUrl}${path}`, options) if (!response.ok) { @@ -71,7 +68,7 @@ export class AuthServiceClient { public tenant = { get: (id: string) => this.request(`/tenant/${id}`, { method: 'GET' }), - create: (data: Omit) => + create: (data: Tenant) => this.request('/tenant', { method: 'POST', headers: { 'Content-Type': 'application/json' }, @@ -83,6 +80,7 @@ export class AuthServiceClient { headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data) }), - delete: (id: string) => this.request(`/tenant/${id}`, { method: 'DELETE' }) + delete: (id: string, deletedAt?: Date) => + this.request(`/tenant/${id}`, { method: 'DELETE' }) } } diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts index 35a273d0bf..c0be42bb28 100644 --- a/packages/backend/src/index.ts +++ b/packages/backend/src/index.ts @@ -221,7 +221,7 @@ export function initIocContainer( return createInMemoryDataStore(config.localCacheDuration) }) - container.singleton('authServiceClient', async () => { + container.singleton('authServiceClient', () => { return new AuthServiceClient(config.authServiceApiUrl) }) @@ -229,8 +229,8 @@ export function initIocContainer( return createTenantService({ logger: await deps.use('logger'), knex: await deps.use('knex'), - apolloClient: await deps.use('apolloClient'), - tenantCache: await deps.use('tenantCache') + tenantCache: await deps.use('tenantCache'), + authServiceClient: deps.use('authServiceClient') }) }) diff --git a/packages/backend/src/tenants/service.test.ts b/packages/backend/src/tenants/service.test.ts index da6d3b7009..86c96e3c10 100644 --- a/packages/backend/src/tenants/service.test.ts +++ b/packages/backend/src/tenants/service.test.ts @@ -9,47 +9,28 @@ import { createTestApp, TestContainer } from '../tests/app' import { TenantService } from './service' import { Config, IAppConfig } from '../config/app' import { truncateTables } from '../tests/tableManager' -import { ApolloClient, NormalizedCacheObject } from '@apollo/client' import { Tenant } from './model' import { getPageTests } from '../shared/baseModel.test' import { Pagination, SortOrder } from '../shared/baseModel' import { createTenant } from '../tests/tenant' import { CacheDataStore } from '../middleware/cache/data-stores' - -const generateMutateGqlError = (path: string = 'createTenant') => ({ - errors: [ - { - message: 'invalid input syntax', - locations: [ - { - line: 1, - column: 1 - } - ], - path: [path], - extensions: { - code: 'INTERNAl_SERVER_ERROR' - } - } - ], - data: null -}) +import { AuthServiceClient } from '../auth-service-client/client' describe('Tenant Service', (): void => { let deps: IocContract let appContainer: TestContainer let tenantService: TenantService let config: IAppConfig - let apolloClient: ApolloClient let knex: Knex + let authServiceClient: AuthServiceClient beforeAll(async (): Promise => { deps = initIocContainer(Config) appContainer = await createTestApp(deps) tenantService = await deps.use('tenantService') config = await deps.use('config') - apolloClient = await deps.use('apolloClient') knex = await deps.use('knex') + authServiceClient = await deps.use('authServiceClient') }) afterEach(async (): Promise => { @@ -126,28 +107,21 @@ describe('Tenant Service', (): void => { idpSecret: 'test-idp-secret' } - const scope = nock(config.authAdminApiUrl) - .post('') - .reply(200, { data: { createTenant: { id: 1234 } } }) + const spy = jest + .spyOn(authServiceClient.tenant, 'create') + .mockImplementationOnce(async () => undefined) - const apolloSpy = jest.spyOn(apolloClient, 'mutate') const tenant = await tenantService.create(createOptions) expect(tenant).toEqual(expect.objectContaining(createOptions)) - expect(apolloSpy).toHaveBeenCalledWith( + expect(spy).toHaveBeenCalledWith( expect.objectContaining({ - variables: { - input: { - id: tenant.id, - idpSecret: createOptions.idpSecret, - idpConsentUrl: createOptions.idpConsentUrl - } - } + id: tenant.id, + idpSecret: createOptions.idpSecret, + idpConsentUrl: createOptions.idpConsentUrl }) ) - - scope.done() }) test('tenant creation rolls back if auth tenant create fails', async (): Promise => { @@ -159,11 +133,13 @@ describe('Tenant Service', (): void => { idpSecret: 'test-idp-secret' } - const scope = nock(config.authAdminApiUrl) - .post('') - .reply(200, generateMutateGqlError('createTenant')) + const spy = jest + .spyOn(authServiceClient.tenant, 'create') + .mockImplementationOnce(() => { + throw new Error() + }) - const apolloSpy = jest.spyOn(apolloClient, 'mutate') + expect.assertions(3) let tenant try { tenant = await tenantService.create(createOptions) @@ -173,19 +149,14 @@ describe('Tenant Service', (): void => { const tenants = await Tenant.query() expect(tenants.length).toEqual(0) - expect(apolloSpy).toHaveBeenCalledWith( + expect(spy).toHaveBeenCalledWith( expect.objectContaining({ - variables: { - input: { - id: expect.any(String), - idpConsentUrl: createOptions.idpConsentUrl, - idpSecret: createOptions.idpSecret - } - } + id: expect.any(String), + idpConsentUrl: createOptions.idpConsentUrl, + idpSecret: createOptions.idpSecret }) ) } - scope.done() }) }) @@ -199,10 +170,10 @@ describe('Tenant Service', (): void => { idpSecret: 'test-idp-secret' } - const scope = nock(config.authAdminApiUrl) - .post('') - .reply(200, { data: { createTenant: { id: 1234 } } }) - .persist() + jest + .spyOn(authServiceClient.tenant, 'create') + .mockImplementationOnce(async () => undefined) + const tenant = await tenantService.create(originalTenantInfo) const updatedTenantInfo = { @@ -214,22 +185,16 @@ describe('Tenant Service', (): void => { idpSecret: 'test-idp-secret-two' } - const apolloSpy = jest.spyOn(apolloClient, 'mutate') + const spy = jest + .spyOn(authServiceClient.tenant, 'update') + .mockImplementationOnce(async () => undefined) const updatedTenant = await tenantService.update(updatedTenantInfo) expect(updatedTenant).toEqual(expect.objectContaining(updatedTenantInfo)) - expect(apolloSpy).toHaveBeenCalledWith( - expect.objectContaining({ - variables: { - input: { - id: tenant.id, - idpConsentUrl: updatedTenantInfo.idpConsentUrl, - idpSecret: updatedTenantInfo.idpSecret - } - } - }) - ) - scope.done() + expect(spy).toHaveBeenCalledWith(tenant.id, { + idpConsentUrl: updatedTenantInfo.idpConsentUrl, + idpSecret: updatedTenantInfo.idpSecret + }) }) test('rolls back tenant if auth tenant update fails', async (): Promise => { @@ -241,9 +206,10 @@ describe('Tenant Service', (): void => { idpSecret: 'test-idp-secret' } - nock(config.authAdminApiUrl) - .post('') - .reply(200, { data: { createTenant: { id: 1234 } } }) + jest + .spyOn(authServiceClient.tenant, 'create') + .mockImplementationOnce(async () => undefined) + const tenant = await tenantService.create(originalTenantInfo) const updatedTenantInfo = { id: tenant.id, @@ -254,13 +220,14 @@ describe('Tenant Service', (): void => { idpSecret: 'test-idp-secret-two' } - nock.cleanAll() + const spy = jest + .spyOn(authServiceClient.tenant, 'update') + .mockImplementationOnce(async () => { + throw new Error() + }) - nock(config.authAdminApiUrl) - .post('') - .reply(200, generateMutateGqlError('updateTenant')) - const apolloSpy = jest.spyOn(apolloClient, 'mutate') let updatedTenant + expect.assertions(3) try { updatedTenant = await tenantService.update(updatedTenantInfo) } catch (err) { @@ -268,20 +235,14 @@ describe('Tenant Service', (): void => { const dbTenant = await Tenant.query().findById(tenant.id) assert.ok(dbTenant) expect(dbTenant).toEqual(expect.objectContaining(originalTenantInfo)) - expect(apolloSpy).toHaveBeenCalledWith( + expect(spy).toHaveBeenCalledWith( + tenant.id, expect.objectContaining({ - variables: { - input: { - id: tenant.id, - idpConsentUrl: updatedTenantInfo.idpConsentUrl, - idpSecret: updatedTenantInfo.idpSecret - } - } + idpConsentUrl: updatedTenantInfo.idpConsentUrl, + idpSecret: updatedTenantInfo.idpSecret }) ) } - - nock.cleanAll() }) test('Cannot update deleted tenant', async (): Promise => { @@ -294,7 +255,7 @@ describe('Tenant Service', (): void => { deletedAt: new Date() }) - const apolloSpy = jest.spyOn(apolloClient, 'mutate') + const spy = jest.spyOn(authServiceClient.tenant, 'update') try { await tenantService.update({ id: dbTenant.id, @@ -307,7 +268,7 @@ describe('Tenant Service', (): void => { assert.ok(dbTenantAfterUpdate) expect(dbTenantAfterUpdate.apiSecret).toEqual(originalSecret) - expect(apolloSpy).toHaveBeenCalledTimes(0) + expect(spy).toHaveBeenCalledTimes(0) } }) }) @@ -322,28 +283,22 @@ describe('Tenant Service', (): void => { idpSecret: 'test-idp-secret' } - const scope = nock(config.authAdminApiUrl) - .post('') - .reply(200, { data: { createTenant: { id: 1234 } } }) - .persist() + jest + .spyOn(authServiceClient.tenant, 'create') + .mockImplementationOnce(async () => undefined) const tenant = await tenantService.create(createOptions) - const apolloSpy = jest.spyOn(apolloClient, 'mutate') + const spy = jest + .spyOn(authServiceClient.tenant, 'delete') + .mockImplementationOnce(async () => undefined) await tenantService.delete(tenant.id) const dbTenant = await Tenant.query().findById(tenant.id) - expect(dbTenant?.deletedAt?.getTime()).toBeLessThanOrEqual( + assert.ok(dbTenant?.deletedAt) + expect(dbTenant.deletedAt.getTime()).toBeLessThanOrEqual( new Date(Date.now()).getTime() ) - expect(apolloSpy).toHaveBeenCalledWith( - expect.objectContaining({ - variables: { - input: { id: tenant.id, deletedAt: dbTenant?.deletedAt } - } - }) - ) - - scope.done() + expect(spy).toHaveBeenCalledWith(tenant.id, dbTenant.deletedAt) }) test('Reverts deletion if auth tenant delete fails', async (): Promise => { @@ -355,17 +310,18 @@ describe('Tenant Service', (): void => { idpSecret: 'test-idp-secret' } - nock(config.authAdminApiUrl) - .post('') - .reply(200, { data: { createTenant: { id: 1234 } } }) + jest + .spyOn(authServiceClient.tenant, 'create') + .mockImplementationOnce(async () => undefined) const tenant = await tenantService.create(createOptions) - nock.cleanAll() + const spy = jest + .spyOn(authServiceClient.tenant, 'delete') + .mockImplementationOnce(async () => { + throw new Error() + }) - const apolloSpy = jest.spyOn(apolloClient, 'mutate') - const deleteScope = nock(config.authAdminApiUrl) - .post('') - .reply(200, generateMutateGqlError('deleteTenant')) + expect.assertions(3) try { await tenantService.delete(tenant.id) } catch (err) { @@ -373,19 +329,8 @@ describe('Tenant Service', (): void => { assert.ok(dbTenant) expect(dbTenant.id).toEqual(tenant.id) expect(dbTenant.deletedAt).toBeNull() - expect(apolloSpy).toHaveBeenCalledWith( - expect.objectContaining({ - variables: { - input: { - id: tenant.id, - deletedAt: expect.any(Date) - } - } - }) - ) + expect(spy).toHaveBeenCalledWith(tenant.id, expect.any(Date)) } - - deleteScope.done() }) }) @@ -395,6 +340,7 @@ describe('Tenant Service', (): void => { let config: IAppConfig let tenantService: TenantService let tenantCache: CacheDataStore + let authServiceClient: AuthServiceClient beforeAll(async (): Promise => { deps = initIocContainer({ @@ -405,6 +351,7 @@ describe('Tenant Service', (): void => { config = await deps.use('config') tenantService = await deps.use('tenantService') tenantCache = await deps.use('tenantCache') + authServiceClient = await deps.use('authServiceClient') }) afterEach(async (): Promise => { @@ -425,10 +372,9 @@ describe('Tenant Service', (): void => { idpSecret: 'test-idp-secret' } - const scope = nock(config.authAdminApiUrl) - .post('') - .reply(200, { data: { createTenant: { tenant: { id: 1234 } } } }) - .persist() + jest + .spyOn(authServiceClient.tenant, 'create') + .mockImplementation(async () => undefined) const spyCacheSet = jest.spyOn(tenantCache, 'set') const tenant = await tenantService.create(createOptions) @@ -447,6 +393,9 @@ describe('Tenant Service', (): void => { expect(spyCacheGet).toHaveBeenCalledWith(tenant.id) const spyCacheUpdateSet = jest.spyOn(tenantCache, 'set') + jest + .spyOn(authServiceClient.tenant, 'update') + .mockImplementation(async () => undefined) const updatedTenant = await tenantService.update({ id: tenant.id, apiSecret: 'test-api-secret-2' @@ -461,6 +410,9 @@ describe('Tenant Service', (): void => { expect(spyCacheUpdateSet).toHaveBeenCalledWith(tenant.id, updatedTenant) const spyCacheDelete = jest.spyOn(tenantCache, 'delete') + jest + .spyOn(authServiceClient.tenant, 'delete') + .mockImplementation(async () => undefined) await tenantService.delete(tenant.id) await expect(tenantService.get(tenant.id)).resolves.toBeUndefined() @@ -468,8 +420,6 @@ describe('Tenant Service', (): void => { // Ensure that cache was set for deletion expect(spyCacheDelete).toHaveBeenCalledTimes(1) expect(spyCacheDelete).toHaveBeenCalledWith(tenant.id) - - scope.done() }) }) }) diff --git a/packages/backend/src/tenants/service.ts b/packages/backend/src/tenants/service.ts index d1973471eb..947881619f 100644 --- a/packages/backend/src/tenants/service.ts +++ b/packages/backend/src/tenants/service.ts @@ -1,10 +1,9 @@ import { Tenant } from './model' import { BaseService } from '../shared/baseService' -import { gql, NormalizedCacheObject } from '@apollo/client' -import { ApolloClient } from '@apollo/client' import { TransactionOrKnex } from 'objection' import { Pagination, SortOrder } from '../shared/baseModel' import { CacheDataStore } from '../middleware/cache/data-stores' +import type { AuthServiceClient } from '../auth-service-client/client' export interface TenantService { get: (id: string) => Promise @@ -16,8 +15,8 @@ export interface TenantService { export interface ServiceDependencies extends BaseService { knex: TransactionOrKnex - apolloClient: ApolloClient tenantCache: CacheDataStore + authServiceClient: AuthServiceClient } export async function createTenantService( @@ -83,26 +82,12 @@ async function createTenant( idpConsentUrl }) - const mutation = gql` - mutation CreateAuthTenant($input: CreateTenantInput!) { - createTenant(input: $input) { - tenant { - id - } - } - } - ` - - const variables = { - input: { - id: tenant.id, - idpSecret, - idpConsentUrl - } - } + await deps.authServiceClient.tenant.create({ + id: tenant.id, + idpSecret, + idpConsentUrl + }) - // TODO: add type to this in https://github.com/interledger/rafiki/issues/3125 - await deps.apolloClient.mutate({ mutation, variables }) await trx.commit() await deps.tenantCache.set(tenant.id, tenant) @@ -143,26 +128,10 @@ async function updateTenant( .throwIfNotFound() if (idpConsentUrl || idpSecret) { - const mutation = gql` - mutation UpdateAuthTenant($input: UpdateTenantInput!) { - updateTenant(input: $input) { - tenant { - id - } - } - } - ` - - const variables = { - input: { - id, - idpConsentUrl, - idpSecret - } - } - - // TODO: add types to this in https://github.com/interledger/rafiki/issues/3125 - await deps.apolloClient.mutate({ mutation, variables }) + await deps.authServiceClient.tenant.update(id, { + idpConsentUrl, + idpSecret + }) } await trx.commit() @@ -186,16 +155,7 @@ async function deleteTenant( await Tenant.query(trx).patchAndFetchById(id, { deletedAt }) - const mutation = gql` - mutation DeleteAuthTenantMutation($input: DeleteTenantInput!) { - deleteTenant(input: $input) { - sucess - } - } - ` - const variables = { input: { id, deletedAt } } - // TODO: add types to this in https://github.com/interledger/rafiki/issues/3125 - await deps.apolloClient.mutate({ mutation, variables }) + await deps.authServiceClient.tenant.delete(id, deletedAt) await trx.commit() } catch (err) { await trx.rollback() diff --git a/packages/backend/src/tests/tenant.ts b/packages/backend/src/tests/tenant.ts index f174a58f2f..579735b73d 100644 --- a/packages/backend/src/tests/tenant.ts +++ b/packages/backend/src/tests/tenant.ts @@ -1,4 +1,3 @@ -import nock from 'nock' import { IocContract } from '@adonisjs/fold' import { faker } from '@faker-js/faker' import { AppServices } from '../app' @@ -17,10 +16,10 @@ export async function createTenant( options?: CreateOptions ): Promise { const tenantService = await deps.use('tenantService') - const config = await deps.use('config') - const scope = nock(config.authAdminApiUrl) - .post('') - .reply(200, { data: { createTenant: { id: 1234 } } }) + const authServiceClient = await deps.use('authServiceClient') + jest + .spyOn(authServiceClient.tenant, 'create') + .mockImplementationOnce(async () => undefined) const tenant = await tenantService.create( options || { email: faker.internet.email(), @@ -30,7 +29,6 @@ export async function createTenant( idpSecret: 'test-idp-secret' } ) - scope.done() if (!tenant) { throw Error('Failed to create test tenant')