Skip to content
Snippets Groups Projects
Commit 737c788c authored by Berend Sliedrecht's avatar Berend Sliedrecht
Browse files

feat(ssi): finished tenants module with e2e tests


Signed-off-by: default avatarBerend Sliedrecht <berend@animo.id>
parent 4468661d
No related branches found
No related tags found
No related merge requests found
This commit is part of merge request !13. Comments created here will be created in the context of that merge request.
Showing
with 356 additions and 44 deletions
......@@ -6,7 +6,7 @@ import { DidsController } from './dids.controller.js';
import { DidsService } from './dids.service.js';
@Module({
imports: [AgentModule],
imports: [AgentModule ],
providers: [DidsService],
controllers: [DidsController],
})
......
import type { AppAgent } from '../agent.service.js';
import type {
EventDidsPublicDidInput,
EventDidsResolveInput,
} from '@ocm/shared';
import { Injectable } from '@nestjs/common';
import { AgentService } from '../agent.service.js';
import { WithTenantService } from '../withTenantService.js';
@Injectable()
export class DidsService {
public agent: AppAgent;
public withTenantService: WithTenantService;
public constructor(agentService: AgentService) {
this.agent = agentService.agent;
public constructor(withTenantService: WithTenantService) {
this.withTenantService = withTenantService;
}
public async resolve(did: string) {
const {
didDocument,
didResolutionMetadata: { message, error },
} = await this.agent.dids.resolve(did);
if (!didDocument) {
throw new Error(
`Could not resolve did: '${did}'. Error: ${error ?? 'None'} Message: ${
message ?? 'None'
}`,
);
}
return didDocument;
public async resolve({ did, tenantId }: EventDidsResolveInput) {
return this.withTenantService.invoke(tenantId, async (t) => {
const {
didDocument,
didResolutionMetadata: { message, error },
} = await t.dids.resolve(did);
if (!didDocument) {
throw new Error(
`Could not resolve did: '${did}'. Error: ${
error ?? 'None'
} Message: ${message ?? 'None'}`,
);
}
return didDocument;
});
}
public async getPublicDid({ tenantId }: EventDidsPublicDidInput) {
return this.withTenantService.invoke(tenantId, async (t) => {
const dids = await t.dids.getCreatedDids({ method: 'indy' });
if (dids.length === 0) {
throw new Error('No registered public DIDs');
}
if (dids.length > 1) {
throw new Error('Multiple public DIDs found');
}
const didRecord = dids[0];
if (!didRecord.didDocument) {
throw new Error(
'A public DID was found, but did not include a DID Document',
);
}
return didRecord.didDocument;
});
}
}
import { TenantRecord } from '@aries-framework/tenants';
import { Test } from '@nestjs/testing';
import { mockConfigModule } from '../../../config/__tests__/mockConfig.js';
import { AgentModule } from '../../agent.module.js';
import { TenantsController } from '../tenants.controller.js';
import { TenantsService } from '../tenants.service.js';
describe('TenantsController', () => {
let tenantsController: TenantsController;
let tenantsService: TenantsService;
beforeEach(async () => {
const moduleRef = await Test.createTestingModule({
imports: [mockConfigModule(), AgentModule],
controllers: [TenantsController],
providers: [TenantsService],
}).compile();
tenantsService = moduleRef.get(TenantsService);
tenantsController = moduleRef.get(TenantsController);
});
describe('resolve', () => {
it('should resolve a basic did', async () => {
const result = new TenantRecord({
config: {
label: 'my-label',
walletConfig: { key: 'some-key', id: 'some-id' },
},
});
jest.spyOn(tenantsService, 'create').mockResolvedValue(result);
const event = await tenantsController.create({
label: 'my-label',
});
expect(event.data).toStrictEqual(result);
});
});
});
import { Controller } from '@nestjs/common';
import { MessagePattern } from '@nestjs/microservices';
import { EventTenantsCreate, EventTenantsCreateInput } from '@ocm/shared';
import { TenantsService } from './tenants.service.js';
@Controller('tenants')
export class TenantsController {
public constructor(private tenantsService: TenantsService) {}
@MessagePattern(EventTenantsCreate.token)
public async create({
label,
}: EventTenantsCreateInput): Promise<EventTenantsCreate> {
return new EventTenantsCreate(
await this.tenantsService.create(label),
undefined,
);
}
}
import { Module } from '@nestjs/common';
import { AgentModule } from '../agent.module.js';
import { TenantsController } from './tenants.controller.js';
import { TenantsService } from './tenants.service.js';
@Module({
imports: [AgentModule],
providers: [TenantsService],
controllers: [TenantsController],
})
export class TenantsModule {}
import type { AppAgent } from '../agent.service.js';
import { Injectable } from '@nestjs/common';
import { AgentService } from '../agent.service.js';
@Injectable()
export class TenantsService {
public agent: AppAgent;
public constructor(agentService: AgentService) {
this.agent = agentService.agent;
}
public async create(label: string) {
return await this.agent.modules.tenants.createTenant({ config: { label } });
}
}
import type { AppAgent } from './agent.service.js';
import { Injectable } from '@nestjs/common';
import { AgentService } from './agent.service.js';
@Injectable()
export class WithTenantService {
private agent: AppAgent;
public constructor(agentService: AgentService) {
this.agent = agentService.agent;
}
public invoke<T>(
tenantId: string,
cb: (tenant: AppAgent) => Promise<T>,
): Promise<T> {
// eslint-disable-next-line no-async-promise-executor
return new Promise<T>(async (resolve, reject) => {
await this.agent.modules.tenants.withTenantAgent(
{ tenantId },
async (tenant) => {
try {
const ret = await cb(tenant as unknown as AppAgent);
resolve(ret);
} catch (e) {
reject(e);
}
},
);
});
}
}
import { DidsModule } from '@aries-framework/core';
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { TerminusModule } from '@nestjs/terminus';
......@@ -5,6 +6,7 @@ import { HealthController } from '@ocm/shared';
import { AgentModule } from './agent/agent.module.js';
import { ConnectionsModule } from './agent/connections/connections.module.js';
import { TenantsModule } from './agent/tenants/tenants.module.js';
import { config } from './config/config.js';
import { validationSchema } from './config/validation.js';
......@@ -18,6 +20,8 @@ import { validationSchema } from './config/validation.js';
}),
AgentModule,
ConnectionsModule,
DidsModule,
TenantsModule,
],
controllers: [HealthController],
})
......
import type { INestApplication } from '@nestjs/common';
import type { ClientProxy } from '@nestjs/microservices';
import type { Observable } from 'rxjs';
import type {
EventDidcommConnectionsGetAllInput,
EventDidcommConnectionsCreateWithSelfInput,
EventDidcommConnectionsGetByIdInput,
EventDidcommConnectionsBlockInput,
} from '@ocm/shared';
import { ClientsModule, Transport } from '@nestjs/microservices';
import { Test } from '@nestjs/testing';
......@@ -14,6 +19,8 @@ import { firstValueFrom } from 'rxjs';
import { AgentModule } from '../src/agent/agent.module.js';
import { ConnectionsModule } from '../src/agent/connections/connections.module.js';
import { TenantsModule } from '../src/agent/tenants/tenants.module.js';
import { TenantsService } from '../src/agent/tenants/tenants.service.js';
import { MetadataTokens } from '../src/common/constants.js';
import { mockConfigModule } from '../src/config/__tests__/mockConfig.js';
......@@ -21,6 +28,7 @@ describe('Connections', () => {
const TOKEN = 'CONNECTIONS_CLIENT_SERVICE';
let app: INestApplication;
let client: ClientProxy;
let tenantId: string;
beforeAll(async () => {
const moduleRef = await Test.createTestingModule({
......@@ -28,6 +36,7 @@ describe('Connections', () => {
mockConfigModule(3004),
AgentModule,
ConnectionsModule,
TenantsModule,
ClientsModule.register([{ name: TOKEN, transport: Transport.NATS }]),
],
}).compile();
......@@ -41,6 +50,10 @@ describe('Connections', () => {
client = app.get(TOKEN);
await client.connect();
const ts = app.get(TenantsService);
const { id } = await ts.create(TOKEN);
tenantId = id;
});
afterAll(async () => {
......@@ -49,10 +62,10 @@ describe('Connections', () => {
});
it(EventDidcommConnectionsGetAll.token, async () => {
const response$: Observable<EventDidcommConnectionsGetAll> = client.send(
EventDidcommConnectionsGetAll.token,
{},
);
const response$ = client.send<
EventDidcommConnectionsGetAll,
EventDidcommConnectionsGetAllInput
>(EventDidcommConnectionsGetAll.token, { tenantId });
const response = await firstValueFrom(response$);
const eventInstance = EventDidcommConnectionsGetAll.fromEvent(response);
......@@ -60,10 +73,13 @@ describe('Connections', () => {
});
it(EventDidcommConnectionsGetById.token, async () => {
const response$: Observable<EventDidcommConnectionsGetById> = client.send(
EventDidcommConnectionsGetById.token,
{ id: 'some-id' },
);
const response$ = client.send<
EventDidcommConnectionsGetById,
EventDidcommConnectionsGetByIdInput
>(EventDidcommConnectionsGetById.token, {
id: 'some-id',
tenantId,
});
const response = await firstValueFrom(response$);
const eventInstance = EventDidcommConnectionsGetById.fromEvent(response);
......@@ -71,8 +87,12 @@ describe('Connections', () => {
});
it(EventDidcommConnectionsCreateWithSelf.token, async () => {
const response$: Observable<EventDidcommConnectionsCreateWithSelf> =
client.send(EventDidcommConnectionsCreateWithSelf.token, {});
const response$ = client.send<
EventDidcommConnectionsCreateWithSelf,
EventDidcommConnectionsCreateWithSelfInput
>(EventDidcommConnectionsCreateWithSelf.token, {
tenantId,
});
const response = await firstValueFrom(response$);
const eventInstance =
......@@ -86,10 +106,13 @@ describe('Connections', () => {
});
it(EventDidcommConnectionsBlock.token, async () => {
const response$: Observable<EventDidcommConnectionsBlock> = client.send(
EventDidcommConnectionsBlock.token,
{ idOrDid: 'some-id' },
);
const response$ = client.send<
EventDidcommConnectionsBlock,
EventDidcommConnectionsBlockInput
>(EventDidcommConnectionsBlock.token, {
idOrDid: 'some-id',
tenantId,
});
const response = await firstValueFrom(response$);
const eventInstance = EventDidcommConnectionsBlock.fromEvent(response);
......
import type { INestApplication } from '@nestjs/common';
import type { ClientProxy } from '@nestjs/microservices';
import type { Observable } from 'rxjs';
import type {
EventDidsResolveInput,
EventDidsPublicDidInput,
} from '@ocm/shared';
import { DidDocument } from '@aries-framework/core';
import { ClientsModule, Transport } from '@nestjs/microservices';
import { Test } from '@nestjs/testing';
import {
EventDidsResolve,
} from '@ocm/shared';
import { EventDidsResolve, EventDidsPublicDid } from '@ocm/shared';
import { firstValueFrom } from 'rxjs';
import { AgentModule } from '../src/agent/agent.module.js';
import { DidsModule } from '../src/agent/dids/dids.module.js';
import { DidsService } from '../src/agent/dids/dids.service.js';
import { TenantsModule } from '../src/agent/tenants/tenants.module.js';
import { TenantsService } from '../src/agent/tenants/tenants.service.js';
import { mockConfigModule } from '../src/config/__tests__/mockConfig.js';
const mockDidDocument = {
context: ['https://w3id.org/did/v1'],
id: 'did:indy:bcovrin:test:7KuDTpQh3GJ7Gp6kErpWvM',
verificationMethod: [
{
id: 'did:indy:bcovrin:test:7KuDTpQh3GJ7Gp6kErpWvM#verkey',
type: 'Ed25519VerificationKey2018',
controller: 'did:indy:bcovrin:test:7KuDTpQh3GJ7Gp6kErpWvM',
publicKeyBase58: '4SySYXQUtuK26zfC7RpQpWYMThfbXphUf8LWyXXmxyTX',
},
],
authentication: ['did:indy:bcovrin:test:7KuDTpQh3GJ7Gp6kErpWvM#verkey'],
};
describe('Dids', () => {
const TOKEN = 'DIDS_CLIENT_SERVICE';
let app: INestApplication;
let client: ClientProxy;
let tenantId: string;
beforeAll(async () => {
jest
.spyOn(DidsService.prototype, 'getPublicDid')
.mockResolvedValue(new DidDocument(mockDidDocument));
const moduleRef = await Test.createTestingModule({
imports: [
mockConfigModule(3005),
AgentModule,
DidsModule,
TenantsModule,
ClientsModule.register([{ name: TOKEN, transport: Transport.NATS }]),
],
}).compile();
......@@ -37,6 +61,10 @@ describe('Dids', () => {
client = app.get(TOKEN);
await client.connect();
const ts = app.get(TenantsService);
const { id } = await ts.create(TOKEN);
tenantId = id;
});
afterAll(async () => {
......@@ -44,11 +72,24 @@ describe('Dids', () => {
client.close();
});
it(EventDidsPublicDid.token, async () => {
const response$ = client.send<EventDidsPublicDid, EventDidsPublicDidInput>(
EventDidsPublicDid.token,
{ tenantId },
);
const response = await firstValueFrom(response$);
const eventInstance = EventDidsPublicDid.fromEvent(response);
expect(eventInstance.instance).toMatchObject(mockDidDocument);
});
it(EventDidsResolve.token, async () => {
const response$: Observable<EventDidsResolve> = client.send(
const response$ = client.send<EventDidsResolve, EventDidsResolveInput>(
EventDidsResolve.token,
{
did: 'did:key:z6MkpTHR8VNsBxYAAWHut2Geadd9jSwuBV8xRoAnwWsdvktH',
tenantId,
},
);
......
......@@ -3,7 +3,7 @@ import config from '../jest.config.js';
/** @type {import('jest').Config} */
export default {
...config,
testTimeout: 12000,
testTimeout: 24000,
rootDir: '.',
testRegex: '.*\\.e2e-spec\\.ts$',
};
import './setEnvVars.js';
import type { INestApplication } from '@nestjs/common';
import type { ClientProxy } from '@nestjs/microservices';
import type { EventTenantsCreateInput } from '@ocm/shared';
import { DidDocument } from '@aries-framework/core';
import { ClientsModule, Transport } from '@nestjs/microservices';
import { Test } from '@nestjs/testing';
import { EventInfoPublicDid } from '@ocm/shared';
import { firstValueFrom, type Observable } from 'rxjs';
import { EventTenantsCreate } from '@ocm/shared';
import { firstValueFrom } from 'rxjs';
import { AgentModule } from '../src/agent/agent.module.js';
import { AgentService } from '../src/agent/agent.service.js';
import { TenantsModule } from '../src/agent/tenants/tenants.module.js';
import { mockConfigModule } from '../src/config/__tests__/mockConfig.js';
const mockDidDocument = {
context: ['https://w3id.org/did/v1'],
id: 'did:indy:bcovrin:test:7KuDTpQh3GJ7Gp6kErpWvM',
verificationMethod: [
{
id: 'did:indy:bcovrin:test:7KuDTpQh3GJ7Gp6kErpWvM#verkey',
type: 'Ed25519VerificationKey2018',
controller: 'did:indy:bcovrin:test:7KuDTpQh3GJ7Gp6kErpWvM',
publicKeyBase58: '4SySYXQUtuK26zfC7RpQpWYMThfbXphUf8LWyXXmxyTX',
},
],
authentication: ['did:indy:bcovrin:test:7KuDTpQh3GJ7Gp6kErpWvM#verkey'],
};
describe('Agent', () => {
const TOKEN = 'AGENT_CLIENT_SERVICE';
describe('Tenants', () => {
const TOKEN = 'TENANTS_CLIENT_SERVICE';
let app: INestApplication;
let client: ClientProxy;
beforeAll(async () => {
jest
.spyOn(AgentService.prototype, 'getPublicDid')
.mockResolvedValue(new DidDocument(mockDidDocument));
const moduleRef = await Test.createTestingModule({
imports: [
mockConfigModule(3000),
mockConfigModule(3005),
AgentModule,
TenantsModule,
ClientsModule.register([{ name: TOKEN, transport: Transport.NATS }]),
],
}).compile();
......@@ -56,20 +37,29 @@ describe('Agent', () => {
await client.connect();
});
it(EventInfoPublicDid.token, async () => {
const response$: Observable<EventInfoPublicDid> = client.send(
EventInfoPublicDid.token,
{},
afterAll(async () => {
await app.close();
client.close();
});
it(EventTenantsCreate.token, async () => {
const response$ = client.send<EventTenantsCreate, EventTenantsCreateInput>(
EventTenantsCreate.token,
{
label: 'my-new-tenant',
},
);
const response = await firstValueFrom(response$);
const eventInstance = EventInfoPublicDid.fromEvent(response);
expect(eventInstance.instance).toMatchObject(mockDidDocument);
});
const eventInstance = EventTenantsCreate.fromEvent(response);
afterAll(async () => {
await app.close();
client.close();
expect(eventInstance.instance.toJSON()).toMatchObject({
config: {
label: 'my-new-tenant',
walletConfig: {
keyDerivationMethod: 'RAW',
},
},
});
});
});
......@@ -682,6 +682,9 @@ importers:
'@aries-framework/core':
specifier: 0.4.2
version: 0.4.2(expo@49.0.18)(react-native@0.72.7)
'@aries-framework/tenants':
specifier: ^0.4.2
version: 0.4.2(expo@49.0.18)(react-native@0.72.7)
'@elastic/ecs-winston-format':
specifier: ^1.5.0
version: 1.5.0
......@@ -758,6 +761,9 @@ importers:
'@aries-framework/node':
specifier: 0.4.2
version: 0.4.2(expo@49.0.18)(react-native@0.72.7)
'@aries-framework/tenants':
specifier: ^0.4.2
version: 0.4.2(expo@49.0.18)(react-native@0.72.7)
'@elastic/ecs-winston-format':
specifier: ^1.5.0
version: 1.5.0
......@@ -1062,6 +1068,19 @@ packages:
- web-streams-polyfill
dev: false
 
/@aries-framework/tenants@0.4.2(expo@49.0.18)(react-native@0.72.7):
resolution: {integrity: sha512-dRgneBY4z6YAn9ieNSeLEqhW+H03aFZwnxcnWhJfSGeHKUl0kMPmjCqvpP3NFhdB/rX92U9OOZDruIv2efM2ig==}
dependencies:
'@aries-framework/core': 0.4.2(expo@49.0.18)(react-native@0.72.7)
async-mutex: 0.4.0
transitivePeerDependencies:
- domexception
- encoding
- expo
- react-native
- web-streams-polyfill
dev: false
/@babel/code-frame@7.10.4:
resolution: {integrity: sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==}
dependencies:
......@@ -6144,6 +6163,12 @@ packages:
dev: false
optional: true
 
/async-mutex@0.4.0:
resolution: {integrity: sha512-eJFZ1YhRR8UN8eBLoNzcDPcy/jqjsg6I1AP+KvWQX80BqOSW1oJPJXDylPUEeMr2ZQvHgnQ//Lp6f3RQ1zI7HA==}
dependencies:
tslib: 2.6.2
dev: false
/async-value-promise@1.1.1:
resolution: {integrity: sha512-c2RFDKjJle1rHa0YxN9Ysu97/QBu3Wa+NOejJxsX+1qVDJrkD3JL/GN1B3gaILAEXJXbu/4Z1lcoCHFESe/APA==}
requiresBuild: true
......@@ -8974,7 +8999,7 @@ packages:
semver: 7.5.4
tapable: 2.2.1
typescript: 5.2.2
webpack: 5.89.0(@swc/core@1.3.96)
webpack: 5.89.0
dev: true
 
/form-data@3.0.1:
......@@ -10313,7 +10338,7 @@ packages:
pretty-format: 29.7.0
slash: 3.0.0
strip-json-comments: 3.1.1
ts-node: 10.9.1(@swc/core@1.3.96)(@types/node@20.9.0)(typescript@5.2.2)
ts-node: 10.9.1(@types/node@20.9.4)(typescript@5.3.2)
transitivePeerDependencies:
- babel-plugin-macros
- supports-color
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment