@sphereon/ssi-sdk.data-store
Version:
1,435 lines (1,247 loc) • 93.8 kB
text/typescript
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