Skip to content

Commit

Permalink
Merge branch 'main' into w3c-migration
Browse files Browse the repository at this point in the history
  • Loading branch information
auer-martin authored Feb 13, 2024
2 parents 1c36fa2 + faa390f commit 091f9f4
Show file tree
Hide file tree
Showing 30 changed files with 607 additions and 200 deletions.
4 changes: 2 additions & 2 deletions packages/anoncreds/src/updates/__tests__/0.3.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ describe('UpdateAssistant | AnonCreds | v0.3.1 - v0.4', () => {
},
])

await updateAssistant.update('0.4')
await updateAssistant.update({ updateToVersion: '0.4' })

expect(await updateAssistant.isUpToDate('0.4')).toBe(true)
expect(await updateAssistant.getNeededUpdates('0.4')).toEqual([])
Expand Down Expand Up @@ -224,7 +224,7 @@ describe('UpdateAssistant | AnonCreds | v0.3.1 - v0.4', () => {
},
])

await updateAssistant.update('0.4')
await updateAssistant.update({ updateToVersion: '0.4' })

expect(await updateAssistant.isUpToDate('0.4')).toBe(true)
expect(await updateAssistant.getNeededUpdates('0.4')).toEqual([])
Expand Down
44 changes: 44 additions & 0 deletions packages/askar/src/__tests__/migration-postgres.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { StorageUpdateService } from '@credo-ts/core'

import { Agent } from '../../../core/src/agent/Agent'
import { CURRENT_FRAMEWORK_STORAGE_VERSION } from '../../../core/src/storage/migration/updates'
import { askarPostgresStorageConfig, getAskarPostgresAgentOptions } from '../../tests/helpers'

const agentOptions = getAskarPostgresAgentOptions('Migration', askarPostgresStorageConfig, {})

describe('migration with postgres backend', () => {
test('Automatic update on agent startup', async () => {
// Initialize agent and set its storage version to 0.1 in order to force automatic update in the next startup
let agent = new Agent(agentOptions)
await agent.initialize()

let storageUpdateService = agent.dependencyManager.resolve(StorageUpdateService)
await storageUpdateService.setCurrentStorageVersion(agent.context, '0.1')
await agent.shutdown()

// Now start agent with auto update storage
agent = new Agent({ ...agentOptions, config: { ...agentOptions.config, autoUpdateStorageOnStartup: true } })
storageUpdateService = agent.dependencyManager.resolve(StorageUpdateService)

// Should fail because export is not supported when using postgres
await expect(agent.initialize()).rejects.toThrow(/backend does not support export/)

expect(await storageUpdateService.getCurrentStorageVersion(agent.context)).toEqual('0.1')
await agent.shutdown()

// Now start agent with auto update storage, but this time disable backup
agent = new Agent({
...agentOptions,
config: { ...agentOptions.config, autoUpdateStorageOnStartup: true, backupBeforeStorageUpdate: false },
})

// Should work OK
await agent.initialize()
expect(await storageUpdateService.getCurrentStorageVersion(agent.context)).toEqual(
CURRENT_FRAMEWORK_STORAGE_VERSION
)
await agent.shutdown()

await agent.wallet.delete()
})
})
24 changes: 12 additions & 12 deletions packages/askar/src/storage/AskarStorageService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,16 @@ export class AskarStorageService<T extends BaseRecord> implements StorageService
/** @inheritDoc */
public async save(agentContext: AgentContext, record: T) {
assertAskarWallet(agentContext.wallet)
const session = agentContext.wallet.session

record.updatedAt = new Date()

const value = JsonTransformer.serialize(record)
const tags = transformFromRecordTagValues(record.getTags()) as Record<string, string>

try {
await session.insert({ category: record.type, name: record.id, value, tags })
await agentContext.wallet.withSession((session) =>
session.insert({ category: record.type, name: record.id, value, tags })
)
} catch (error) {
if (isAskarError(error, AskarErrorCode.Duplicate)) {
throw new RecordDuplicateError(`Record with id ${record.id} already exists`, { recordType: record.type })
Expand All @@ -34,15 +35,16 @@ export class AskarStorageService<T extends BaseRecord> implements StorageService
/** @inheritDoc */
public async update(agentContext: AgentContext, record: T): Promise<void> {
assertAskarWallet(agentContext.wallet)
const session = agentContext.wallet.session

record.updatedAt = new Date()

const value = JsonTransformer.serialize(record)
const tags = transformFromRecordTagValues(record.getTags()) as Record<string, string>

try {
await session.replace({ category: record.type, name: record.id, value, tags })
await agentContext.wallet.withSession((session) =>
session.replace({ category: record.type, name: record.id, value, tags })
)
} catch (error) {
if (isAskarError(error, AskarErrorCode.NotFound)) {
throw new RecordNotFoundError(`record with id ${record.id} not found.`, {
Expand All @@ -58,10 +60,9 @@ export class AskarStorageService<T extends BaseRecord> implements StorageService
/** @inheritDoc */
public async delete(agentContext: AgentContext, record: T) {
assertAskarWallet(agentContext.wallet)
const session = agentContext.wallet.session

try {
await session.remove({ category: record.type, name: record.id })
await agentContext.wallet.withSession((session) => session.remove({ category: record.type, name: record.id }))
} catch (error) {
if (isAskarError(error, AskarErrorCode.NotFound)) {
throw new RecordNotFoundError(`record with id ${record.id} not found.`, {
Expand All @@ -80,10 +81,9 @@ export class AskarStorageService<T extends BaseRecord> implements StorageService
id: string
): Promise<void> {
assertAskarWallet(agentContext.wallet)
const session = agentContext.wallet.session

try {
await session.remove({ category: recordClass.type, name: id })
await agentContext.wallet.withSession((session) => session.remove({ category: recordClass.type, name: id }))
} catch (error) {
if (isAskarError(error, AskarErrorCode.NotFound)) {
throw new RecordNotFoundError(`record with id ${id} not found.`, {
Expand All @@ -98,10 +98,11 @@ export class AskarStorageService<T extends BaseRecord> implements StorageService
/** @inheritDoc */
public async getById(agentContext: AgentContext, recordClass: BaseRecordConstructor<T>, id: string): Promise<T> {
assertAskarWallet(agentContext.wallet)
const session = agentContext.wallet.session

try {
const record = await session.fetch({ category: recordClass.type, name: id })
const record = await agentContext.wallet.withSession((session) =>
session.fetch({ category: recordClass.type, name: id })
)
if (!record) {
throw new RecordNotFoundError(`record with id ${id} not found.`, {
recordType: recordClass.type,
Expand All @@ -117,9 +118,8 @@ export class AskarStorageService<T extends BaseRecord> implements StorageService
/** @inheritDoc */
public async getAll(agentContext: AgentContext, recordClass: BaseRecordConstructor<T>): Promise<T[]> {
assertAskarWallet(agentContext.wallet)
const session = agentContext.wallet.session

const records = await session.fetchAll({ category: recordClass.type })
const records = await agentContext.wallet.withSession((session) => session.fetchAll({ category: recordClass.type }))

const instances = []
for (const record of records) {
Expand Down
69 changes: 37 additions & 32 deletions packages/askar/src/storage/__tests__/AskarStorageService.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,13 +59,15 @@ describe('AskarStorageService', () => {
},
})

const retrieveRecord = await ariesAskar.sessionFetch({
category: record.type,
name: record.id,
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
sessionHandle: wallet.session.handle!,
forUpdate: false,
})
const retrieveRecord = await wallet.withSession((session) =>
ariesAskar.sessionFetch({
category: record.type,
name: record.id,
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
sessionHandle: session.handle!,
forUpdate: false,
})
)

expect(JSON.parse(retrieveRecord?.getTags(0) ?? '{}')).toEqual({
someBoolean: '1',
Expand All @@ -81,31 +83,34 @@ describe('AskarStorageService', () => {
})

it('should correctly transform tag values from string after retrieving', async () => {
await ariesAskar.sessionUpdate({
category: TestRecord.type,
name: 'some-id',
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
sessionHandle: wallet.session.handle!,
value: TypedArrayEncoder.fromString('{}'),
tags: {
someBoolean: '1',
someOtherBoolean: '0',
someStringValue: 'string',
// Before 0.5.0, there was a bug where array values that contained a : would be incorrectly
// parsed back into a record as we would split on ':' and thus only the first part would be included
// in the record as the tag value. If the record was never re-saved it would work well, as well as if the
// record tag was generated dynamically before save (as then the incorrectly transformed back value would be
// overwritten again on save).
'anArrayValueWhereValuesContainColon:foo:bar:test': '1',
'anArrayValueWhereValuesContainColon:https://google.com': '1',
'anArrayValue:foo': '1',
'anArrayValue:bar': '1',
// booleans are stored as '1' and '0' so we store the string values '1' and '0' as 'n__1' and 'n__0'
someStringNumberValue: 'n__1',
anotherStringNumberValue: 'n__0',
},
operation: 0, // EntryOperation.Insert
})
await wallet.withSession(
async (session) =>
await ariesAskar.sessionUpdate({
category: TestRecord.type,
name: 'some-id',
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
sessionHandle: session.handle!,
value: TypedArrayEncoder.fromString('{}'),
tags: {
someBoolean: '1',
someOtherBoolean: '0',
someStringValue: 'string',
// Before 0.5.0, there was a bug where array values that contained a : would be incorrectly
// parsed back into a record as we would split on ':' and thus only the first part would be included
// in the record as the tag value. If the record was never re-saved it would work well, as well as if the
// record tag was generated dynamically before save (as then the incorrectly transformed back value would be
// overwritten again on save).
'anArrayValueWhereValuesContainColon:foo:bar:test': '1',
'anArrayValueWhereValuesContainColon:https://google.com': '1',
'anArrayValue:foo': '1',
'anArrayValue:bar': '1',
// booleans are stored as '1' and '0' so we store the string values '1' and '0' as 'n__1' and 'n__0'
someStringNumberValue: 'n__1',
anotherStringNumberValue: 'n__0',
},
operation: 0, // EntryOperation.Insert
})
)

const record = await storageService.getById(agentContext, TestRecord, 'some-id')

Expand Down
Loading

0 comments on commit 091f9f4

Please sign in to comment.