UNPKG

@sphereon/ssi-sdk.data-store

Version:

1,435 lines (1,247 loc) • 93.8 kB
import { getDID } from '@sphereon/ssi-sdk-ext.did-utils' import { DataSources } from '@sphereon/ssi-sdk.agent-config' import { CredentialRole } from '@sphereon/ssi-types' import { DataSource, FindOptionsWhere } from 'typeorm' import { afterEach, beforeEach, describe, expect, it } from 'vitest' import { BaseContactEntity } from '../entities/contact/BaseContactEntity' import { ConnectionEntity } from '../entities/contact/ConnectionEntity' import { ContactMetadataItemEntity } from '../entities/contact/ContactMetadataItemEntity' import { CorrelationIdentifierEntity } from '../entities/contact/CorrelationIdentifierEntity' import { DidAuthConfigEntity } from '../entities/contact/DidAuthConfigEntity' import { ElectronicAddressEntity } from '../entities/contact/ElectronicAddressEntity' import { IdentityEntity } from '../entities/contact/IdentityEntity' import { IdentityMetadataItemEntity } from '../entities/contact/IdentityMetadataItemEntity' import { NaturalPersonEntity } from '../entities/contact/NaturalPersonEntity' import { OpenIdConfigEntity } from '../entities/contact/OpenIdConfigEntity' import { OrganizationEntity } from '../entities/contact/OrganizationEntity' import { PartyEntity } from '../entities/contact/PartyEntity' import { PartyRelationshipEntity } from '../entities/contact/PartyRelationshipEntity' import { PartyTypeEntity } from '../entities/contact/PartyTypeEntity' import { PhysicalAddressEntity } from '../entities/contact/PhysicalAddressEntity' import { contactMetadataItemEntityFrom, DataStoreEntitiesWithVeramo, DataStoreMigrationsWithVeramo, identityMetadataItemEntityFrom, partyTypeFrom } from '../index' import { IdentityOrigin, MetadataTypes, PartyOrigin, ConnectionType, CorrelationIdentifierType, NaturalPerson, NonPersistedConnection, NonPersistedDidAuthConfig, NonPersistedElectronicAddress, NonPersistedIdentity, NonPersistedNaturalPerson, NonPersistedOpenIdConfig, NonPersistedOrganization, NonPersistedParty, NonPersistedPartyType, NonPersistedPhysicalAddress, Organization, PartyTypeType, } from '@sphereon/ssi-sdk.data-store-types' import { connectionEntityFrom, didAuthConfigEntityFrom, electronicAddressEntityFrom, identityEntityFrom, naturalPersonEntityFrom, openIdConfigEntityFrom, organizationEntityFrom, partyEntityFrom, partyRelationshipEntityFrom, partyTypeEntityFrom, physicalAddressEntityFrom, } from '../utils/contact/MappingUtils' // TODO write test adding two contacts reusing the same contactType describe('Database entities tests', (): void => { let dbConnection: DataSource beforeEach(async (): Promise<void> => { DataSources.singleInstance().defaultDbType = 'sqlite' dbConnection = await new DataSource({ type: 'sqlite', database: ':memory:', logging: ['info'], migrationsRun: false, migrations: DataStoreMigrationsWithVeramo, synchronize: false, entities: DataStoreEntitiesWithVeramo, }).initialize() await dbConnection.runMigrations() expect(await dbConnection.showMigrations()).toBeFalsy() }) afterEach(async (): Promise<void> => { await (await dbConnection).destroy() }) it('Should save person party to database', async (): Promise<void> => { const party: NonPersistedParty = { uri: 'example.com', partyType: { type: PartyTypeType.NATURAL_PERSON, origin: PartyOrigin.INTERNAL, tenantId: '0605761c-4113-4ce5-a6b2-9cbae2f9d289', name: 'example_name', }, contact: { firstName: 'example_first_name', middleName: 'example_middle_name', lastName: 'example_last_name', displayName: 'example_display_name', }, } const partyEntity: PartyEntity = partyEntityFrom(party) const savedParty: PartyEntity = await dbConnection.getRepository(PartyEntity).save(partyEntity, { transaction: true, }) const fromDb: PartyEntity | null = await dbConnection.getRepository(PartyEntity).findOne({ where: { id: savedParty.id }, }) expect(fromDb).toBeDefined() expect(fromDb?.identities?.length).toEqual(0) expect(fromDb?.uri).toEqual(party.uri) expect(fromDb?.partyType).toBeDefined() expect(fromDb?.partyType.type).toEqual(party.partyType.type) expect(fromDb?.partyType.origin).toEqual(party.partyType.origin) expect(fromDb?.partyType.tenantId).toEqual(party.partyType.tenantId) expect(fromDb?.partyType.name).toEqual(party.partyType.name) expect(fromDb?.contact).toBeDefined() expect((<NaturalPersonEntity>fromDb?.contact).firstName).toEqual((<NaturalPerson>party.contact).firstName) expect((<NaturalPersonEntity>fromDb?.contact).middleName).toEqual((<NaturalPerson>party.contact).middleName) expect((<NaturalPersonEntity>fromDb?.contact).lastName).toEqual((<NaturalPerson>party.contact).lastName) expect((<NaturalPersonEntity>fromDb?.contact).displayName).toEqual((<NaturalPerson>party.contact).displayName) }) it('Should save organization party to database', async (): Promise<void> => { const party: NonPersistedParty = { uri: 'example.com', partyType: { type: PartyTypeType.ORGANIZATION, origin: PartyOrigin.INTERNAL, tenantId: '0605761c-4113-4ce5-a6b2-9cbae2f9d289', name: 'example_name', }, contact: { legalName: 'example_legal_name', displayName: 'example_display_name', }, } const partyEntity: PartyEntity = partyEntityFrom(party) const savedParty: PartyEntity = await dbConnection.getRepository(PartyEntity).save(partyEntity, { transaction: true, }) const fromDb: PartyEntity | null = await dbConnection.getRepository(PartyEntity).findOne({ where: { id: savedParty.id }, }) expect(fromDb).toBeDefined() expect(fromDb?.identities?.length).toEqual(0) expect(fromDb?.uri).toEqual(party.uri) expect(fromDb?.partyType).toBeDefined() expect(fromDb?.partyType.type).toEqual(party.partyType.type) expect(fromDb?.partyType.origin).toEqual(party.partyType.origin) expect(fromDb?.partyType.tenantId).toEqual(party.partyType.tenantId) expect(fromDb?.partyType.name).toEqual(party.partyType.name) expect(fromDb?.contact).toBeDefined() expect((<OrganizationEntity>fromDb?.contact).legalName).toEqual((<Organization>party.contact).legalName) expect((<OrganizationEntity>fromDb?.contact).displayName).toEqual((<Organization>party.contact).displayName) }) it('Should result in party relationship for the owner side only', async (): Promise<void> => { const party1: NonPersistedParty = { uri: 'example1.com', partyType: { type: PartyTypeType.NATURAL_PERSON, origin: PartyOrigin.INTERNAL, tenantId: '0605761c-4113-4ce5-a6b2-9cbae2f9d289', name: 'example_name1', }, contact: { firstName: 'example_first_name1', middleName: 'example_middle_name1', lastName: 'example_last_name1', displayName: 'example_display_name1', }, } const partyEntity1: PartyEntity = partyEntityFrom(party1) const savedParty1: PartyEntity = await dbConnection.getRepository(PartyEntity).save(partyEntity1, { transaction: true, }) const party2: NonPersistedParty = { uri: 'example2.com', partyType: { type: PartyTypeType.NATURAL_PERSON, origin: PartyOrigin.INTERNAL, tenantId: '0605761c-4113-4ce5-a6b2-9cbae2f9d288', name: 'example_name2', }, contact: { firstName: 'example_first_name2', middleName: 'example_middle_name2', lastName: 'example_last_name2', displayName: 'example_display_name2', }, } const partyEntity2: PartyEntity = partyEntityFrom(party2) const savedParty2: PartyEntity = await dbConnection.getRepository(PartyEntity).save(partyEntity2, { transaction: true, }) const relationship: PartyRelationshipEntity = partyRelationshipEntityFrom({ leftId: savedParty1.id, rightId: savedParty2.id, }) await dbConnection.getRepository(PartyRelationshipEntity).save(relationship, { transaction: true, }) const fromDb1: PartyEntity | null = await dbConnection.getRepository(PartyEntity).findOne({ where: { id: savedParty1.id }, }) const fromDb2: PartyEntity | null = await dbConnection.getRepository(PartyEntity).findOne({ where: { id: savedParty2.id }, }) expect(fromDb1).toBeDefined() expect(fromDb1?.relationships.length).toEqual(1) expect(fromDb2).toBeDefined() expect(fromDb2?.relationships.length).toEqual(0) }) it('should throw error when saving person party with blank first name', async (): Promise<void> => { const party: NonPersistedParty = { uri: 'example.com', partyType: { type: PartyTypeType.NATURAL_PERSON, origin: PartyOrigin.INTERNAL, tenantId: '0605761c-4113-4ce5-a6b2-9cbae2f9d289', name: 'example_name', }, contact: { firstName: '', middleName: 'example_middle_name1', lastName: 'example_last_name1', displayName: 'example_display_name1', }, } const partyEntity: PartyEntity = partyEntityFrom(party) await expect(dbConnection.getRepository(PartyEntity).save(partyEntity)).rejects.toThrowError('Blank first names are not allowed') }) it('should throw error when saving person party with blank middle name', async (): Promise<void> => { const party: NonPersistedParty = { uri: 'example.com', partyType: { type: PartyTypeType.NATURAL_PERSON, origin: PartyOrigin.EXTERNAL, tenantId: '0605761c-4113-4ce5-a6b2-9cbae2f9d289', name: 'example_name', }, contact: { firstName: 'example_first_name', middleName: '', lastName: 'example_last_name', displayName: 'example_display_name', }, } const partyEntity: PartyEntity = partyEntityFrom(party) await expect(dbConnection.getRepository(PartyEntity).save(partyEntity)).rejects.toThrowError('Blank middle names are not allowed') }) it('should throw error when saving person party with blank last name', async (): Promise<void> => { const party: NonPersistedParty = { uri: 'example.com', partyType: { type: PartyTypeType.NATURAL_PERSON, origin: PartyOrigin.EXTERNAL, tenantId: '0605761c-4113-4ce5-a6b2-9cbae2f9d289', name: 'example_name', }, contact: { firstName: 'example_first_name', middleName: 'example_middle_name', lastName: '', displayName: 'example_display_name', }, } const partyEntity: PartyEntity = partyEntityFrom(party) await expect(dbConnection.getRepository(PartyEntity).save(partyEntity)).rejects.toThrowError('Blank last names are not allowed') }) it('should throw error when saving person party with blank display name', async (): Promise<void> => { const party: NonPersistedParty = { uri: 'example.com', partyType: { type: PartyTypeType.NATURAL_PERSON, origin: PartyOrigin.EXTERNAL, tenantId: '0605761c-4113-4ce5-a6b2-9cbae2f9d289', name: 'example_name', }, contact: { firstName: 'example_first_name', middleName: 'example_middle_name', lastName: 'example_last_name', displayName: '', }, } const partyEntity: PartyEntity = partyEntityFrom(party) await expect(dbConnection.getRepository(PartyEntity).save(partyEntity)).rejects.toThrowError('Blank display names are not allowed') }) it('should throw error when saving organization party with blank legal name', async (): Promise<void> => { const party: NonPersistedParty = { uri: 'example.com', partyType: { type: PartyTypeType.ORGANIZATION, origin: PartyOrigin.INTERNAL, tenantId: '0605761c-4113-4ce5-a6b2-9cbae2f9d289', name: 'example_name', }, contact: { legalName: '', displayName: 'example_legal_name', }, } const partyEntity: PartyEntity = partyEntityFrom(party) await expect(dbConnection.getRepository(PartyEntity).save(partyEntity)).rejects.toThrowError('Blank legal names are not allowed') }) it('should throw error when saving organization party with blank display name', async (): Promise<void> => { const party: NonPersistedParty = { uri: 'example.com', partyType: { type: PartyTypeType.ORGANIZATION, origin: PartyOrigin.INTERNAL, tenantId: '0605761c-4113-4ce5-a6b2-9cbae2f9d289', name: 'example_name', }, contact: { legalName: 'example_first_name', displayName: '', }, } const partyEntity: PartyEntity = partyEntityFrom(party) await expect(dbConnection.getRepository(PartyEntity).save(partyEntity)).rejects.toThrowError('Blank display names are not allowed') }) it('should throw error when saving party with blank party type name', async (): Promise<void> => { const party: NonPersistedParty = { uri: 'example.com', partyType: { type: PartyTypeType.NATURAL_PERSON, origin: PartyOrigin.EXTERNAL, tenantId: '0605761c-4113-4ce5-a6b2-9cbae2f9d289', name: '', }, contact: { firstName: 'example_first_name', middleName: 'example_middle_name', lastName: 'example_last_name', displayName: 'example_display_name', }, } const partyEntity: PartyEntity = partyEntityFrom(party) await expect(dbConnection.getRepository(PartyEntity).save(partyEntity)).rejects.toThrowError('Blank names are not allowed') }) it('should throw error when saving party with blank party type description', async (): Promise<void> => { const party: NonPersistedParty = { uri: 'example.com', partyType: { type: PartyTypeType.NATURAL_PERSON, origin: PartyOrigin.INTERNAL, tenantId: '0605761c-4113-4ce5-a6b2-9cbae2f9d289', name: 'example_name', description: '', }, contact: { firstName: 'example_first_name', middleName: 'example_middle_name', lastName: 'example_last_name', displayName: 'example_display_name', }, } const partyEntity: PartyEntity = partyEntityFrom(party) await expect(dbConnection.getRepository(PartyEntity).save(partyEntity)).rejects.toThrowError('Blank descriptions are not allowed') }) it('should throw error when saving party with blank party type tenant id', async (): Promise<void> => { const party: NonPersistedParty = { uri: 'example.com', partyType: { type: PartyTypeType.NATURAL_PERSON, origin: PartyOrigin.EXTERNAL, tenantId: '', name: 'example_name', }, contact: { firstName: 'example_first_name', middleName: 'example_middle_name', lastName: 'example_last_name', displayName: 'example_display_name', }, } const partyEntity: PartyEntity = partyEntityFrom(party) await expect(dbConnection.getRepository(PartyEntity).save(partyEntity)).rejects.toThrowError("Blank tenant id's are not allowed") }) it('Should enforce unique alias for an identity', async (): Promise<void> => { const alias = 'non_unique_alias' const identity1: NonPersistedIdentity = { alias, origin: IdentityOrigin.EXTERNAL, roles: [CredentialRole.ISSUER, CredentialRole.VERIFIER], identifier: { type: CorrelationIdentifierType.DID, correlationId: 'unique_correlationId1', }, } const identity1Entity: IdentityEntity = identityEntityFrom(identity1) await dbConnection.getRepository(IdentityEntity).save(identity1Entity) const identity2: NonPersistedIdentity = { alias: alias, origin: IdentityOrigin.EXTERNAL, roles: [CredentialRole.ISSUER, CredentialRole.VERIFIER], identifier: { type: CorrelationIdentifierType.DID, correlationId: 'unique_correlationId2', }, } const identity2Entity: IdentityEntity = identityEntityFrom(identity2) await expect(dbConnection.getRepository(IdentityEntity).save(identity2Entity)).rejects.toThrowError( 'SQLITE_CONSTRAINT: UNIQUE constraint failed: Identity.alias', ) }) it('Should enforce unique correlationId for a identity', async (): Promise<void> => { const correlationId = 'non_unique_correlationId' const identity1: NonPersistedIdentity = { alias: 'unique_alias1', origin: IdentityOrigin.EXTERNAL, roles: [CredentialRole.ISSUER, CredentialRole.VERIFIER], identifier: { type: CorrelationIdentifierType.DID, correlationId, }, } const identity1Entity: IdentityEntity = identityEntityFrom(identity1) await dbConnection.getRepository(IdentityEntity).save(identity1Entity) const identity2: NonPersistedIdentity = { alias: 'unique_alias2', origin: IdentityOrigin.EXTERNAL, roles: [CredentialRole.ISSUER, CredentialRole.VERIFIER], identifier: { type: CorrelationIdentifierType.DID, correlationId, }, } const identity2Entity: IdentityEntity = identityEntityFrom(identity2) await expect(dbConnection.getRepository(IdentityEntity).save(identity2Entity)).rejects.toThrowError( 'SQLITE_CONSTRAINT: UNIQUE constraint failed: CorrelationIdentifier.correlation_id', ) }) it('Should save identity to database', async (): Promise<void> => { const correlationId = 'example_did' const identity: NonPersistedIdentity = { alias: correlationId, origin: IdentityOrigin.EXTERNAL, roles: [CredentialRole.ISSUER, CredentialRole.VERIFIER], identifier: { type: CorrelationIdentifierType.DID, correlationId, }, } const identityEntity: IdentityEntity = identityEntityFrom(identity) await dbConnection.getRepository(IdentityEntity).save(identityEntity) const fromDb: IdentityEntity | null = await dbConnection.getRepository(IdentityEntity).findOne({ where: { identifier: { correlationId, }, }, }) expect(fromDb).toBeDefined() expect(fromDb?.connection).toBeNull() expect(fromDb?.identifier).toBeDefined() expect(fromDb?.identifier.correlationId).toEqual(identity.identifier.correlationId) expect(fromDb?.identifier.type).toEqual(identity.identifier.type) }) it('should throw error when saving identity with blank alias', async (): Promise<void> => { const identity: NonPersistedIdentity = { alias: '', origin: IdentityOrigin.EXTERNAL, roles: [CredentialRole.ISSUER, CredentialRole.VERIFIER], identifier: { type: CorrelationIdentifierType.DID, correlationId: 'example_did', }, } const identityEntity: IdentityEntity = identityEntityFrom(identity) await expect(dbConnection.getRepository(IdentityEntity).save(identityEntity)).rejects.toThrowError('Blank aliases are not allowed') }) it('should throw error when saving identity with blank correlation id', async (): Promise<void> => { const identity: NonPersistedIdentity = { alias: 'example_did', origin: IdentityOrigin.EXTERNAL, roles: [CredentialRole.ISSUER, CredentialRole.VERIFIER], identifier: { type: CorrelationIdentifierType.DID, correlationId: '', }, } const identityEntity: IdentityEntity = identityEntityFrom(identity) await expect(dbConnection.getRepository(IdentityEntity).save(identityEntity)).rejects.toThrowError('Blank correlation ids are not allowed') }) it('should throw error when saving identity with blank metadata label', async (): Promise<void> => { const correlationId = 'example_did' const identity: NonPersistedIdentity = { alias: correlationId, origin: IdentityOrigin.EXTERNAL, roles: [CredentialRole.ISSUER, CredentialRole.VERIFIER], identifier: { type: CorrelationIdentifierType.DID, correlationId, }, metadata: [ { label: '', value: 'example_value', }, ], } const identityEntity: IdentityEntity = identityEntityFrom(identity) await expect(dbConnection.getRepository(IdentityEntity).save(identityEntity)).rejects.toThrowError('Blank metadata labels are not allowed') }) it('Should save identity with openid connection to database', async (): Promise<void> => { const correlationId = 'example.com' const identity: NonPersistedIdentity = { alias: correlationId, origin: IdentityOrigin.EXTERNAL, roles: [CredentialRole.ISSUER, CredentialRole.VERIFIER], identifier: { type: CorrelationIdentifierType.URL, correlationId, }, connection: { type: ConnectionType.OPENID_CONNECT, config: { clientId: '138d7bf8-c930-4c6e-b928-97d3a4928b01', clientSecret: '03b3955f-d020-4f2a-8a27-4e452d4e27a0', scopes: ['auth'], issuer: 'https://example.com/app-test', redirectUrl: 'app:/callback', dangerouslyAllowInsecureHttpRequests: true, clientAuthMethod: <const>'post', }, }, } const identityEntity: IdentityEntity = identityEntityFrom(identity) await dbConnection.getRepository(IdentityEntity).save(identityEntity) const fromDb: IdentityEntity | null = await dbConnection.getRepository(IdentityEntity).findOne({ where: { identifier: { correlationId, }, }, }) expect(fromDb).toBeDefined() expect(fromDb?.connection).toBeDefined() expect(fromDb?.identifier).toBeDefined() expect(fromDb?.identifier.correlationId).toEqual(identity.identifier.correlationId) expect(fromDb?.identifier.type).toEqual(identity.identifier.type) expect(fromDb?.connection?.type).toEqual(identity.connection?.type) expect(fromDb?.connection?.config).toBeDefined() expect((<OpenIdConfigEntity>fromDb?.connection?.config).clientId).toEqual((<NonPersistedOpenIdConfig>identity.connection?.config).clientId) }) it('Should save identity with didauth connection to database', async (): Promise<void> => { const correlationId = 'example.com' const identity: NonPersistedIdentity = { alias: correlationId, origin: IdentityOrigin.EXTERNAL, roles: [CredentialRole.ISSUER, CredentialRole.VERIFIER], identifier: { type: CorrelationIdentifierType.URL, correlationId, }, connection: { type: ConnectionType.SIOPv2, config: { idOpts: { identifier: { did: 'did:test:138d7bf8-c930-4c6e-b928-97d3a4928b01', provider: 'test_provider', keys: [], services: [], }, }, redirectUrl: 'https://example.com', stateId: 'e91f3510-5ce9-42ee-83b7-fa68ff323d27', sessionId: 'https://example.com/did:test:138d7bf8-c930-4c6e-b928-97d3a4928b01', }, }, } satisfies NonPersistedIdentity const identityEntity: IdentityEntity = identityEntityFrom(identity) await dbConnection.getRepository(IdentityEntity).save(identityEntity) const fromDb: IdentityEntity | null = await dbConnection.getRepository(IdentityEntity).findOne({ where: { identifier: { correlationId, }, }, }) expect(fromDb).toBeDefined() expect(fromDb?.connection).toBeDefined() expect(fromDb?.identifier).toBeDefined() expect(fromDb?.identifier.correlationId).toEqual(identity.identifier.correlationId) expect(fromDb?.identifier.type).toEqual(identity.identifier.type) expect(fromDb?.connection?.type).toEqual(identity.connection?.type) expect(fromDb?.connection?.config).toBeDefined() expect((<DidAuthConfigEntity>fromDb?.connection?.config).identifier).toEqual( getDID({ identifier: (<NonPersistedDidAuthConfig>identity.connection?.config).idOpts.identifier as string }), ) }) it('Should save connection with openid config to database', async (): Promise<void> => { const connection: NonPersistedConnection = { type: ConnectionType.OPENID_CONNECT, config: { clientId: '138d7bf8-c930-4c6e-b928-97d3a4928b01', clientSecret: '03b3955f-d020-4f2a-8a27-4e452d4e27a0', scopes: ['auth'], issuer: 'https://example.com/app-test', redirectUrl: 'app:/callback', dangerouslyAllowInsecureHttpRequests: true, clientAuthMethod: <const>'post', }, } const connectionEntity: ConnectionEntity = connectionEntityFrom(connection) await dbConnection.getRepository(ConnectionEntity).save(connectionEntity, { transaction: true, }) const fromDb: ConnectionEntity | null = await dbConnection.getRepository(ConnectionEntity).findOne({ where: { type: connection.type }, }) expect(fromDb).toBeDefined() const fromDbConfig: OpenIdConfigEntity | null = await dbConnection.getRepository(OpenIdConfigEntity).findOne({ where: { id: fromDb?.id }, }) expect(fromDbConfig).toBeDefined() expect(fromDb?.type).toEqual(connection.type) expect(fromDb?.config).toBeDefined() expect((<OpenIdConfigEntity>fromDb?.config).clientId).toEqual((<NonPersistedOpenIdConfig>connection.config).clientId) }) it('Should save connection with didauth config to database', async (): Promise<void> => { const connection: NonPersistedConnection = { type: ConnectionType.SIOPv2, config: { idOpts: { identifier: { did: 'did:test:138d7bf8-c930-4c6e-b928-97d3a4928b01', provider: 'test_provider', keys: [], services: [], }, }, redirectUrl: 'https://example.com', stateId: 'e91f3510-5ce9-42ee-83b7-fa68ff323d27', sessionId: 'https://example.com/did:test:138d7bf8-c930-4c6e-b928-97d3a4928b01', }, } const connectionEntity: ConnectionEntity = connectionEntityFrom(connection) await dbConnection.getRepository(ConnectionEntity).save(connectionEntity, { transaction: true, }) const fromDb: ConnectionEntity | null = await dbConnection.getRepository(ConnectionEntity).findOne({ where: { type: connection.type }, }) expect(fromDb).toBeDefined() const fromDbConfig: DidAuthConfigEntity | null = await dbConnection.getRepository(DidAuthConfigEntity).findOne({ where: { id: fromDb?.id }, }) expect(fromDbConfig).toBeDefined() expect(fromDb?.type).toEqual(connection.type) expect(fromDb?.config).toBeDefined() expect((<DidAuthConfigEntity>fromDb?.config).identifier).toEqual( getDID({ identifier: (<NonPersistedDidAuthConfig>connection?.config).idOpts.identifier as string }), ) }) it('Should save openid config to database', async (): Promise<void> => { const clientId = '138d7bf8-c930-4c6e-b928-97d3a4928b01' const config: NonPersistedOpenIdConfig = { clientId, clientSecret: '03b3955f-d020-4f2a-8a27-4e452d4e27a0', scopes: ['auth'], issuer: 'https://example.com/app-test', redirectUrl: 'app:/callback', dangerouslyAllowInsecureHttpRequests: true, clientAuthMethod: <const>'post', } const configEntity: OpenIdConfigEntity = openIdConfigEntityFrom(config) await dbConnection.getRepository(OpenIdConfigEntity).save(configEntity, { transaction: true, }) const fromDb: OpenIdConfigEntity | null = await dbConnection.getRepository(OpenIdConfigEntity).findOne({ where: { clientId: config.clientId }, }) expect(fromDb).toBeDefined() expect((<OpenIdConfigEntity>fromDb).clientId).toEqual(config.clientId) }) it('Should save didauth config to database', async (): Promise<void> => { const sessionId = 'https://example.com/did:test:138d7bf8-c930-4c6e-b928-97d3a4928b01' const config: NonPersistedDidAuthConfig = { idOpts: { identifier: { did: 'did:test:138d7bf8-c930-4c6e-b928-97d3a4928b01', provider: 'test_provider', keys: [], services: [], }, }, redirectUrl: 'https://example.com', stateId: 'e91f3510-5ce9-42ee-83b7-fa68ff323d27', sessionId, } const configEntity: DidAuthConfigEntity = didAuthConfigEntityFrom(config) await dbConnection.getRepository(DidAuthConfigEntity).save(configEntity, { transaction: true, }) const fromDb: DidAuthConfigEntity | null = await dbConnection.getRepository(DidAuthConfigEntity).findOne({ where: { sessionId: config.sessionId }, }) expect(fromDb).toBeDefined() expect((<DidAuthConfigEntity>fromDb).identifier).toEqual(getDID({ identifier: (<NonPersistedDidAuthConfig>config).idOpts.identifier as string })) }) it('Should delete party and all child relations', async (): Promise<void> => { const party1: NonPersistedParty = { uri: 'example.com', partyType: { type: PartyTypeType.NATURAL_PERSON, origin: PartyOrigin.INTERNAL, tenantId: '0605761c-4113-4ce5-a6b2-9cbae2f9d289', name: 'example_name1', }, contact: { firstName: 'example_first_name1', middleName: 'example_middle_name1', lastName: 'example_last_name1', displayName: 'example_display_name1', }, } const partyEntity1: PartyEntity = partyEntityFrom(party1) const savedParty1: PartyEntity | null = await dbConnection.getRepository(PartyEntity).save(partyEntity1) expect(savedParty1).toBeDefined() const party2: NonPersistedParty = { uri: 'example.com', partyType: { type: PartyTypeType.NATURAL_PERSON, origin: PartyOrigin.INTERNAL, tenantId: '0605761c-4113-4ce5-a6b2-9cbae2f9d288', name: 'example_name2', }, contact: { firstName: 'example_first_name2', middleName: 'example_middle_name2', lastName: 'example_last_name2', displayName: 'example_display_name2', }, } const partyEntity2: PartyEntity = partyEntityFrom(party2) const savedParty2: PartyEntity | null = await dbConnection.getRepository(PartyEntity).save(partyEntity2) expect(savedParty2).toBeDefined() const correlationId = 'relation_example.com' const identity: NonPersistedIdentity = { alias: correlationId, origin: IdentityOrigin.EXTERNAL, roles: [CredentialRole.ISSUER, CredentialRole.VERIFIER], identifier: { type: CorrelationIdentifierType.URL, correlationId, }, connection: { type: ConnectionType.OPENID_CONNECT, config: { clientId: '138d7bf8-c930-4c6e-b928-97d3a4928b01', clientSecret: '03b3955f-d020-4f2a-8a27-4e452d4e27a0', scopes: ['auth'], issuer: 'https://example.com/app-test', redirectUrl: 'app:/callback', dangerouslyAllowInsecureHttpRequests: true, clientAuthMethod: <const>'post', }, }, metadata: [ { label: 'example_label', value: 'example_value', }, ], } const identityEntity: IdentityEntity = identityEntityFrom(identity) identityEntity.party = savedParty1 const savedIdentity: IdentityEntity | null = await dbConnection.getRepository(IdentityEntity).save(identityEntity) expect(savedIdentity).toBeDefined() const electronicAddress: NonPersistedElectronicAddress = { type: 'email', electronicAddress: 'example_electronic_address', } const electronicAddressEntity: ElectronicAddressEntity = electronicAddressEntityFrom(electronicAddress) electronicAddressEntity.party = savedParty1 const savedElectronicAddress: ElectronicAddressEntity | null = await dbConnection .getRepository(ElectronicAddressEntity) .save(electronicAddressEntity) expect(savedElectronicAddress).toBeDefined() const relationship: PartyRelationshipEntity = partyRelationshipEntityFrom({ leftId: savedParty1.id, rightId: savedParty2.id, }) const savedRelationship: PartyRelationshipEntity | null = await dbConnection.getRepository(PartyRelationshipEntity).save(relationship, { transaction: true, }) expect(savedRelationship).toBeDefined() expect( await dbConnection.getRepository(PartyEntity).findOne({ where: { id: savedParty1.id }, }), ).toBeDefined() await dbConnection.getRepository(PartyEntity).delete({ id: savedParty1.id }) // check party await expect( await dbConnection.getRepository(PartyEntity).findOne({ where: { id: savedParty1.id }, }), ).toBeNull() // check identity expect( await dbConnection.getRepository(IdentityEntity).findOne({ where: { id: savedParty1.id }, }), ).toBeNull() // check identity identifier expect( await dbConnection.getRepository(CorrelationIdentifierEntity).findOne({ where: { id: savedIdentity.identifier.id }, }), ).toBeNull() // check identity connection expect( await dbConnection.getRepository(ConnectionEntity).findOne({ where: { id: savedIdentity.connection!.id }, }), ).toBeNull() // check connection config expect( await dbConnection.getRepository(OpenIdConfigEntity).findOne({ where: { id: savedIdentity.connection!.config.id }, }), ).toBeNull() // check identity metadata expect( await dbConnection.getRepository(IdentityMetadataItemEntity).findOne({ where: { id: savedIdentity.metadata![0].id }, }), ).toBeNull() // check electronic address expect( await dbConnection.getRepository(ElectronicAddressEntity).findOne({ where: { id: savedParty1.id }, }), ).toBeNull() // check contact expect( await dbConnection.getRepository(BaseContactEntity).findOne({ where: { id: savedParty1.contact.id }, }), ).toBeNull() // check party type expect( await dbConnection.getRepository(PartyTypeEntity).findOne({ where: { id: savedParty1.partyType.id }, }), ).toBeDefined() // check relation expect( await dbConnection.getRepository(PartyRelationshipEntity).findOne({ where: { id: savedRelationship.id }, }), ).toBeNull() }) it('Should delete identity and all child relations', async (): Promise<void> => { const party: NonPersistedParty = { uri: 'example.com', partyType: { type: PartyTypeType.NATURAL_PERSON, origin: PartyOrigin.EXTERNAL, tenantId: '0605761c-4113-4ce5-a6b2-9cbae2f9d289', name: 'example_name', }, contact: { firstName: 'example_first_name', middleName: 'example_middle_name', lastName: 'example_last_name', displayName: 'example_display_name', }, } const partyEntity: PartyEntity = partyEntityFrom(party) const savedParty: PartyEntity | null = await dbConnection.getRepository(PartyEntity).save(partyEntity) expect(savedParty).toBeDefined() const correlationId = 'relation_example.com' const identity: NonPersistedIdentity = { alias: correlationId, origin: IdentityOrigin.EXTERNAL, roles: [CredentialRole.ISSUER, CredentialRole.VERIFIER], identifier: { type: CorrelationIdentifierType.URL, correlationId, }, connection: { type: ConnectionType.SIOPv2, config: { idOpts: { identifier: { did: 'did:test:138d7bf8-c930-4c6e-b928-97d3a4928b01', provider: 'test_provider', keys: [], services: [], }, }, redirectUrl: 'https://example.com', stateId: 'e91f3510-5ce9-42ee-83b7-fa68ff323d27', sessionId: 'https://example.com/did:test:138d7bf8-c930-4c6e-b928-97d3a4928b01', }, }, metadata: [ { label: 'example_label', value: 'example_value', }, ], } const identityEntity: IdentityEntity = identityEntityFrom(identity) identityEntity.party = savedParty const savedIdentity: IdentityEntity | null = await dbConnection.getRepository(IdentityEntity).save(identityEntity) expect( await dbConnection.getRepository(PartyEntity).findOne({ where: { id: savedParty.id }, }), ).toBeDefined() await dbConnection.getRepository(IdentityEntity).delete({ id: savedIdentity.id }) // check identity expect( await dbConnection.getRepository(IdentityEntity).findOne({ where: { alias: correlationId }, }), ).toBeNull() // check identity identifier expect( await dbConnection.getRepository(CorrelationIdentifierEntity).findOne({ where: { id: savedIdentity.identifier.id }, }), ).toBeNull() // check identity connection expect( await dbConnection.getRepository(ConnectionEntity).findOne({ where: { id: savedIdentity.connection!.id }, }), ).toBeNull() // check connection config expect( await dbConnection.getRepository(OpenIdConfigEntity).findOne({ where: { id: savedIdentity.connection!.config.id }, }), ).toBeNull() // check identity metadata expect( await dbConnection.getRepository(IdentityMetadataItemEntity).findOne({ where: { id: savedIdentity.metadata![0].id }, }), ).toBeNull() }) it('Should not delete party when deleting identity', async (): Promise<void> => { const party: NonPersistedParty = { uri: 'example.com', partyType: { type: PartyTypeType.NATURAL_PERSON, origin: PartyOrigin.EXTERNAL, tenantId: '0605761c-4113-4ce5-a6b2-9cbae2f9d289', name: 'example_name', }, contact: { firstName: 'example_first_name', middleName: 'example_middle_name', lastName: 'example_last_name', displayName: 'example_display_name', }, } const partyEntity: PartyEntity = partyEntityFrom(party) const savedParty: PartyEntity | null = await dbConnection.getRepository(PartyEntity).save(partyEntity) expect(savedParty).toBeDefined() const correlationId = 'relation_example.com' const identity: NonPersistedIdentity = { alias: correlationId, origin: IdentityOrigin.EXTERNAL, roles: [CredentialRole.ISSUER, CredentialRole.VERIFIER], identifier: { type: CorrelationIdentifierType.URL, correlationId, }, connection: { type: ConnectionType.SIOPv2, config: { idOpts: { identifier: { did: 'did:test:138d7bf8-c930-4c6e-b928-97d3a4928b01', provider: 'test_provider', keys: [], services: [], }, }, redirectUrl: 'https://example.com', stateId: 'e91f3510-5ce9-42ee-83b7-fa68ff323d27', sessionId: 'https://example.com/did:test:138d7bf8-c930-4c6e-b928-97d3a4928b01', }, }, metadata: [ { label: 'example_label', value: 'example_value', }, ], } const identityEntity: IdentityEntity = identityEntityFrom(identity) identityEntity.party = savedParty const savedIdentity: IdentityEntity | null = await dbConnection.getRepository(IdentityEntity).save(identityEntity) expect(savedIdentity).toBeDefined() await dbConnection.getRepository(IdentityEntity).delete({ id: savedIdentity.id }) // check identity expect( await dbConnection.getRepository(IdentityEntity).findOne({ where: { id: savedIdentity.id }, }), ).toBeNull() // check party expect( await dbConnection.getRepository(PartyEntity).findOne({ where: { id: savedParty.id }, }), ).toBeDefined() }) it('Should set creation date when saving party', async (): Promise<void> => { const party: NonPersistedParty = { uri: 'example.com', partyType: { type: PartyTypeType.NATURAL_PERSON, origin: PartyOrigin.INTERNAL, tenantId: '0605761c-4113-4ce5-a6b2-9cbae2f9d289', name: 'example_name', }, contact: { firstName: 'example_first_name', middleName: 'example_middle_name', lastName: 'example_last_name', displayName: 'example_display_name', }, } const partyEntity: PartyEntity = partyEntityFrom(party) const savedParty: PartyEntity | null = await dbConnection.getRepository(PartyEntity).save(partyEntity) const fromDb: PartyEntity | null = await dbConnection.getRepository(PartyEntity).findOne({ where: { id: savedParty.id }, }) expect(fromDb).toBeDefined() expect(fromDb?.createdAt).toBeDefined() }) it('Should not update creation date when updating party', async (): Promise<void> => { const party: NonPersistedParty = { uri: 'example.com', partyType: { type: PartyTypeType.NATURAL_PERSON, origin: PartyOrigin.INTERNAL, tenantId: '0605761c-4113-4ce5-a6b2-9cbae2f9d289', name: 'example_name', }, contact: { firstName: 'example_first_name', middleName: 'example_middle_name', lastName: 'example_last_name', displayName: 'example_display_name', }, } const partyEntity: PartyEntity = partyEntityFrom(party) const savedParty: PartyEntity | null = await dbConnection.getRepository(PartyEntity).save(partyEntity) expect(savedParty).toBeDefined() const newContactFirstName = 'new_first_name' await dbConnection.getRepository(PartyEntity).save({ ...savedParty, contact: { ...savedParty.contact, firstName: newContactFirstName, }, }) const fromDb: PartyEntity | null = await dbConnection.getRepository(PartyEntity).findOne({ where: { id: savedParty.id }, }) expect(fromDb).toBeDefined() expect((<NaturalPersonEntity>fromDb?.contact).firstName).toEqual(newContactFirstName) expect(fromDb?.createdAt).toEqual(savedParty?.createdAt) }) it('Should set creation date when saving identity', async (): Promise<void> => { const correlationId = 'example_did' const identity: NonPersistedIdentity = { alias: correlationId, origin: IdentityOrigin.EXTERNAL, roles: [CredentialRole.ISSUER, CredentialRole.VERIFIER], identifier: { type: CorrelationIdentifierType.DID, correlationId, }, } const identityEntity: IdentityEntity = identityEntityFrom(identity) await dbConnection.getRepository(IdentityEntity).save(identityEntity) const fromDb: IdentityEntity | null = await dbConnection.getRepository(IdentityEntity).findOne({ where: { identifier: { correlationId, }, }, }) expect(fromDb).toBeDefined() expect(fromDb?.createdAt).toBeDefined() }) it('Should not update creation date when saving identity', async (): Promise<void> => { const correlationId = 'example_did' const identity: NonPersistedIdentity = { alias: correlationId, origin: IdentityOrigin.EXTERNAL, roles: [CredentialRole.ISSUER, CredentialRole.VERIFIER], identifier: { type: CorrelationIdentifierType.DID, correlationId, }, } const identityEntity: IdentityEntity = identityEntityFrom(identity) const savedIdentity: IdentityEntity | null = await dbConnection.getRepository(IdentityEntity).save(identityEntity) const newCorrelationId = 'new_example_did' await dbConnection .getRepository(IdentityEntity) .save({ ...savedIdentity, identifier: { ...savedIdentity.identifier, correlationId: newCorrelationId } }) const fromDb: IdentityEntity | null = await dbConnection.getRepository(IdentityEntity).findOne({ where: { identifier: { correlationId: newCorrelationId, }, }, }) expect(fromDb).toBeDefined() expect(fromDb?.createdAt).toEqual(savedIdentity?.createdAt) }) it('Should set last updated date when saving party', async (): Promise<void> => { const party: NonPersistedParty = { uri: 'example.com', partyType: { type: PartyTypeType.NATURAL_PERSON, origin: PartyOrigin.INTERNAL, tenantId: '0605761c-4113-4ce5-a6b2-9cbae2f9d289', name: 'example_name', }, contact: { firstName: 'example_first_name', middleName: 'example_middle_name', lastName: 'example_last_name', displayName: 'example_display_name', }, } const partyEntity: PartyEntity = partyEntityFrom(party) const savedParty: PartyEntity | null = await dbConnection.getRepository(PartyEntity).save(partyEntity) const fromDb: PartyEntity | null = await dbConnection.getRepository(PartyEntity).findOne({ where: { id: savedParty.id }, }) expect(fromDb).toBeDefined() expect(fromDb?.lastUpdatedAt).toBeDefined() }) it('Should update last updated date when updating party', async (): Promise<void> => { const party: NonPersistedParty = { uri: 'example.com', partyType: { type: PartyTypeType.NATURAL_PERSON, origin: PartyOrigin.EXTERNAL, tenantId: '0605761c-4113-4ce5-a6b2-9cbae2f9d289', name: 'example_name', }, contact: { firstName: 'example_first_name', middleName: 'example_middle_name', lastName: 'example_last_name', displayName: 'example_display_name', }, } const partyEntity: PartyEntity = partyEntityFrom(party) const savedParty: PartyEntity | null = await dbConnection.getRepository(PartyEntity).save(partyEntity) expect(savedParty).toBeDefined() // waiting here to get a different timestamp await new Promise((resolve) => setTimeout(resolve, 2000)) const newContactFirstName = 'new_first_name' await dbConnection.getRepository(PartyEntity).save({ ...savedParty, // FIXME there is still an issue when updating nested objects, the parent does not update // https://github.com/typeorm/typeorm/issues/5378 uri: 'new uri', // TODO remove this to trigger the bug contact: { ...savedParty.contact, firstName: newContactFirstName, }, }) const fromDb: PartyEntity | null = await dbConnection.getRepository(PartyEntity).findOne({ where: { id: savedParty.id }, }) expect(fromDb).toBeDefined() expect((<NaturalPersonEntity>fromDb?.contact).firstName).toEqual(newContactFirstName) expect(fromDb?.lastUpdatedAt).not.toEqual(savedParty?.lastUpdatedAt) }) it('Should set last updated date when saving party type', async (): Promise<void> => { const partyType: NonPersistedPartyType = { type: PartyTypeType.NATURAL_PERSON, origin: PartyOrigin.EXTERNAL, tenantId: '0605761c-4113-4ce5-a6b2-9cbae2f9d289', name: 'example_name', } const partyTypeEntity: PartyTypeEntity = partyTypeEntityFrom(partyType) const savedPartyType: PartyTypeEntity | null = await dbConnection.getRepository(PartyTypeEntity).save(partyTypeEntity) const fromDb: PartyTypeEntity | null = await dbConnection.getRepository(PartyTypeEntity).findOne({ where: { id: savedPartyType.id }, }) expect(fromDb).toBeDefined() expect(fromDb?.lastUpdatedAt).toBeDefined() }) it('Should set last creation date when saving party type', async (): Promise<void> => { const partyType: NonPersistedPartyType = { type: PartyTypeType.NATURAL_PERSON, origin: PartyOrigin.INTERNAL, tenantId: '0605761c-4113-4ce5-a6b2-9cbae2f9d289', name: 'example_name', } const partyTypeEntity: PartyTypeEntity = partyTypeEntityFrom(partyType) const savedPartyType: PartyTypeEntity | null = await dbConnection.getRepository(PartyTypeEntity).save(partyTypeEntity) const fromDb: PartyTypeEntity | null = await dbConnection.getRepository(PartyTypeEntity).findOne({ where: { id: savedPartyType.id }, }) expect(fromDb).toBeDefined() expect(fromDb?.createdAt).toBeDefined() }) it('Should set last updated date when saving identity', async (): Promise<void> => { const correlationId = 'example_did' const identity: NonPersistedIdentity = { alias: correlationId, origin: IdentityOrigin.EXTERNAL, roles: [CredentialRole.ISSUER, CredentialRole.VERIFIER], identifier: { type: CorrelationIdentifierType.DID, correlationId, }, } const identityEntity: IdentityEntity = identityEntityFrom(identity) await dbConnection.getRepository(IdentityEntity).save(identityEntity) const fromDb: IdentityEntity | null = await dbConnection.getRepository(IdentityEntity).findOne({ where: { identifier: { correlationId, }, }, }) expect(fromDb).toBeDefined() expect(fromDb?.lastUpdatedAt).toBeDefined() }) it('Should enforce unique type and tenant id combination when saving party type', async (): Promise<void> => { const tenantId = 'non_unique_value' const partyType1: NonPersistedPartyType = { type: PartyTypeType.NATURAL_PERSON, origin: PartyOrigin.EXTERNAL, tenantId, name: 'example_party_type_name1', } const partyTypeEntity1: PartyTypeEntity = partyTypeEntityFrom(partyType1) const savedPartyType1: PartyTypeEntity | null = await dbConnection.getRepository(PartyTypeEntity).save(partyTypeEntity1) expect(savedPartyType1).toBeDefined() const partyType2: NonPersistedPartyType = { type: PartyTypeType.NATURAL_PERSON, origin: PartyOrigin.INTERNAL, tenantId, name: 'examp