Skip to content

Commit

Permalink
fix(data-store): support credentials with undefined issuanceDate
Browse files Browse the repository at this point in the history
BREAKING CHANGE: this changes the NULL constraints in the database, requiring a migration. A migration has been added to the default migration set provided by Veramo, but it is otherwise a breaking change if you are not using that set.
  • Loading branch information
mirceanis committed Oct 13, 2023
1 parent 28c65f7 commit d43fa30
Show file tree
Hide file tree
Showing 9 changed files with 256 additions and 38 deletions.
20 changes: 19 additions & 1 deletion __tests__/initial.migration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import {
} from '../packages/data-store/src'
import { KeyManager } from '../packages/key-manager/src'
import { DIDManager } from '../packages/did-manager/src'
import { CredentialPlugin } from "../packages/credential-w3c/src";
import { FakeDidProvider, FakeDidResolver } from '../packages/test-utils/src'

import { DataSource, DataSourceOptions } from 'typeorm'
Expand Down Expand Up @@ -135,12 +136,13 @@ describe('database initial migration tests', () => {
new DataStore(dbConnection),
new DataStoreORM(dbConnection),
new DIDComm(),
new CredentialPlugin()
],
})
return true
})
afterAll(async () => {
await (await dbConnection).close()
await (await dbConnection).destroy()
fs.unlinkSync(databaseFile)
})

Expand Down Expand Up @@ -260,6 +262,22 @@ describe('database initial migration tests', () => {
const msg = await agent.unpackDIDCommMessage(packed)
expect(msg.message.body).toEqual({ hello: 'world' })
})

it('saves credential with undefined issuanceDate', async () => {
const issuer = await agent.didManagerCreate({ provider: 'did:key' })
const cred = await agent.createVerifiableCredential({
credential: {
issuer: issuer.did,
credentialSubject: {
name: 'Alice',
},
issuanceDate: undefined,
},
proofFormat: 'jwt',
})
const stored = await agent.dataStoreSaveVerifiableCredential({ verifiableCredential: cred })
expect(stored).toBeDefined()
})
})
}
})
2 changes: 1 addition & 1 deletion __tests__/localAgent.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -264,7 +264,7 @@ const setup = async (options?: IAgentOptions): Promise<boolean> => {
const tearDown = async (): Promise<boolean> => {
try {
await (await dbConnection).dropDatabase()
await (await dbConnection).close()
await (await dbConnection).destroy()
} catch (e) {
// nop
}
Expand Down
1 change: 1 addition & 0 deletions __tests__/localMemoryStoreAgent.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,7 @@ const setup = async (options?: IAgentOptions): Promise<boolean> => {

const tearDown = async (): Promise<boolean> => {
try {
await dbConnection?.dropDatabase()
await dbConnection?.destroy()
} catch (e) {
// nop
Expand Down
2 changes: 1 addition & 1 deletion __tests__/restAgent.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,7 @@ const tearDown = async (): Promise<boolean> => {
await new Promise((resolve, reject) => restServer.close(resolve))
try {
await (await dbConnection).dropDatabase()
await (await dbConnection).close()
await (await dbConnection).destroy()
} catch (e) {
// nop
}
Expand Down
138 changes: 109 additions & 29 deletions __tests__/shared/saveClaims.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
TAgent,
} from '../../packages/core-types/src'
import { ISelectiveDisclosure } from '../../packages/selective-disclosure/src'
import { beforeAll } from '@jest/globals'

type ConfiguredAgent = TAgent<
IDIDManager & ICredentialIssuer & IDataStoreORM & IDataStore & IMessageHandler & ISelectiveDisclosure
Expand All @@ -28,13 +29,9 @@ export default (testContext: {
beforeAll(async () => {
await testContext.setup()
agent = testContext.getAgent()
})
afterAll(testContext.tearDown)

it('should create identifier', async () => {
identifier = await agent.didManagerCreate({ kms: 'local' })
expect(identifier).toHaveProperty('did')
})
afterAll(testContext.tearDown)

it('should create verifiable credentials', async () => {
// Looping these in a map/forEach throws SQL UNIQUE CONSTRAINT errors
Expand Down Expand Up @@ -123,59 +120,56 @@ export default (testContext: {
{ column: 'value', value: ['math', 'art'] },
],
order: [{ column: 'issuanceDate', direction: 'DESC' }],
take: 1
take: 1,
})
expect(credentials).toHaveLength(1)
})
it('should be able to limit credentials when searching and sorting', async () => {
const credentials = await agent.dataStoreORMGetVerifiableCredentials({
where: [
{ column: 'type', value: ['VerifiableCredential'] },
],
where: [{ column: 'type', value: ['VerifiableCredential'] }],
order: [{ column: 'issuanceDate', direction: 'DESC' }],
take: 1,
skip: 1
skip: 1,
})
expect(credentials).toHaveLength(1)
})

it('should be able to limit credentials when sorting', async () => {
const credentialsAllDesc = await agent.dataStoreORMGetVerifiableCredentials({
order: [{ column: 'issuanceDate', direction: 'DESC' }]
order: [{ column: 'issuanceDate', direction: 'DESC' }],
})

const credentialsAllAsc = await agent.dataStoreORMGetVerifiableCredentials({
order: [{ column: 'issuanceDate', direction: 'ASC' }]
order: [{ column: 'issuanceDate', direction: 'ASC' }],
})

const credentialsIdAllDesc = await agent.dataStoreORMGetVerifiableCredentials({
order: [{ column: 'id', direction: 'DESC' }]
order: [{ column: 'id', direction: 'DESC' }],
})

const credentialsIdAllAsc = await agent.dataStoreORMGetVerifiableCredentials({
order: [{ column: 'id', direction: 'ASC' }]
order: [{ column: 'id', direction: 'ASC' }],
})


const credentials1 = await agent.dataStoreORMGetVerifiableCredentials({
order: [{ column: 'issuanceDate', direction: 'DESC' }],
take: 1,
skip: 0
skip: 0,
})
const credentials2 = await agent.dataStoreORMGetVerifiableCredentials({
order: [{ column: 'issuanceDate', direction: 'DESC' }],
take: 1,
skip: 1
skip: 1,
})
const credentials3 = await agent.dataStoreORMGetVerifiableCredentials({
order: [{ column: 'issuanceDate', direction: 'ASC' }],
take: 2,
skip: 0
skip: 0,
})
const credentials4 = await agent.dataStoreORMGetVerifiableCredentials({
order: [{ column: 'issuanceDate', direction: 'ASC' }],
take: 2,
skip: 1
skip: 1,
})

expect(credentialsAllDesc).toHaveLength(3)
Expand All @@ -190,18 +184,30 @@ export default (testContext: {
expect(credentialsIdAllDesc[1].verifiableCredential.id).toEqual('b')
expect(credentialsIdAllDesc[2].verifiableCredential.id).toEqual('a')

expect(credentialsAllDesc[0].verifiableCredential.issuanceDate).toEqual(credentials1[0].verifiableCredential.issuanceDate)
expect(credentialsAllDesc[1].verifiableCredential.issuanceDate).toEqual(credentials2[0].verifiableCredential.issuanceDate)

expect(credentialsAllDesc[0].verifiableCredential.issuanceDate).toEqual(credentialsAllAsc[2].verifiableCredential.issuanceDate)
expect(credentialsAllDesc[1].verifiableCredential.issuanceDate).toEqual(credentialsAllAsc[1].verifiableCredential.issuanceDate)
expect(credentialsAllDesc[2].verifiableCredential.issuanceDate).toEqual(credentialsAllAsc[0].verifiableCredential.issuanceDate)
expect(credentialsAllDesc[0].verifiableCredential.issuanceDate).toEqual(
credentials1[0].verifiableCredential.issuanceDate,
)
expect(credentialsAllDesc[1].verifiableCredential.issuanceDate).toEqual(
credentials2[0].verifiableCredential.issuanceDate,
)

expect(credentialsAllDesc[0].verifiableCredential.issuanceDate).toEqual(
credentialsAllAsc[2].verifiableCredential.issuanceDate,
)
expect(credentialsAllDesc[1].verifiableCredential.issuanceDate).toEqual(
credentialsAllAsc[1].verifiableCredential.issuanceDate,
)
expect(credentialsAllDesc[2].verifiableCredential.issuanceDate).toEqual(
credentialsAllAsc[0].verifiableCredential.issuanceDate,
)

expect(new Date(credentials1[0].verifiableCredential.issuanceDate).getTime())
.toBeGreaterThan(new Date(credentials2[0].verifiableCredential.issuanceDate).getTime())
expect(new Date(credentials1[0].verifiableCredential.issuanceDate).getTime()).toBeGreaterThan(
new Date(credentials2[0].verifiableCredential.issuanceDate).getTime(),
)

expect(new Date(credentials4[0].verifiableCredential.issuanceDate).getTime())
.toBeGreaterThan(new Date(credentials3[0].verifiableCredential.issuanceDate).getTime())
expect(new Date(credentials4[0].verifiableCredential.issuanceDate).getTime()).toBeGreaterThan(
new Date(credentials3[0].verifiableCredential.issuanceDate).getTime(),
)
})

it('should be able to delete credential', async () => {
Expand All @@ -216,4 +222,78 @@ export default (testContext: {
expect(credentials2).toHaveLength(2)
})
})

describe('credential queries', () => {
let agent: ConfiguredAgent

beforeAll(async () => {
await testContext.setup()
agent = testContext.getAgent()
})

it('should query by type and issuer', async () => {
const issuer = await agent.didManagerCreate({ kms: 'local' })
const cred1 = await agent.createVerifiableCredential({
credential: {
issuer: { id: issuer.did },
type: ['Test123'],
credentialSubject:{
hello: 'world',
}
},
proofFormat: 'jwt',
})
const credentialHash = await agent.dataStoreSaveVerifiableCredential({ verifiableCredential: cred1 })

const found = await agent.dataStoreORMGetVerifiableCredentials({
where: [
{ column: 'type', value: ['VerifiableCredential,Test123'] },
{ column: 'issuer', value: [issuer.did] },
],
})
expect(found).toHaveLength(1)
expect(found[0].hash).toEqual(credentialHash)
})

it('should query by type and issuer with orderby', async () => {
const issuer = await agent.didManagerCreate({ kms: 'local' })

const cred1 = await agent.createVerifiableCredential({
credential: {
issuer: { id: issuer.did },
type: ['Test321'],
credentialSubject:{
first: true,
},
issuanceDate: undefined // intentionally use a nullish looking value here
},
proofFormat: 'jwt',
})
const cred1Hash = await agent.dataStoreSaveVerifiableCredential({ verifiableCredential: cred1 })

const cred2 = await agent.createVerifiableCredential({
credential: {
issuer: { id: issuer.did },
type: ['Test321'],
credentialSubject:{
first: false,
},
issuanceDate: '2000-01-01T00:00:00Z',
},
proofFormat: 'jwt',
})
const cred2Hash = await agent.dataStoreSaveVerifiableCredential({ verifiableCredential: cred2 })

const found = await agent.dataStoreORMGetVerifiableCredentials({
where: [
{ column: 'type', value: ['VerifiableCredential,Test321'] },
{ column: 'issuer', value: [issuer.did] },
],
order: [{ column: 'issuanceDate', direction: 'DESC' }],
take: 1
})
expect(found).toHaveLength(1)
expect(found[0].hash).toEqual(cred2Hash)
})
})
}
5 changes: 3 additions & 2 deletions packages/data-store/src/entities/claim.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,10 @@ export class Claim extends BaseEntity {
// @ts-ignore
credential: Relation<Credential>

@Column()
// The VC data model does not allow credentials without an issuance date, but some credentials from the wild may
@Column({ nullable: true })
// @ts-ignore
issuanceDate: Date
issuanceDate?: Date

@Column({ nullable: true })
expirationDate?: Date
Expand Down
8 changes: 4 additions & 4 deletions packages/data-store/src/entities/credential.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,7 @@ import { asArray, computeEntryHash, extractIssuer } from '@veramo/utils'
/**
* Represents some common properties of a Verifiable Credential that are stored in a TypeORM database for querying.
*
* @see {@link @veramo/core-types#IDataStoreORM.dataStoreORMGetVerifiableCredentials | dataStoreORMGetVerifiableCredentials}
* for the interface defining how this can be queried.
* @see {@link @veramo/core-types#IDataStoreORM.dataStoreORMGetVerifiableCredentials | dataStoreORMGetVerifiableCredentials} for the interface defining how this can be queried.
*
* @see {@link @veramo/data-store#DataStoreORM | DataStoreORM} for the implementation of the query interface.
*
Expand Down Expand Up @@ -65,9 +64,10 @@ export class Credential extends BaseEntity {
@Column({ nullable: true })
id?: string

@Column()
// The VC data model does not allow credentials without an issuance date, but some credentials from the wild may
@Column({ nullable: true })
// @ts-ignore
issuanceDate: Date
issuanceDate?: Date

@Column({ nullable: true })
expirationDate?: Date
Expand Down
Loading

0 comments on commit d43fa30

Please sign in to comment.