feat(suite-sync): separate quota check from getting keys

# Conflicts:
#	suite-common/suite-sync-types/src/refreshSuiteSyncKeys.ts

# Conflicts:
#	suite-common/suite-sync-types/src/refreshSuiteSyncKeys.ts
#	suite-common/suite-sync/src/createRefreshSuiteSyncKeys.ts
This commit is contained in:
juriczech
2026-02-12 13:54:06 +01:00
committed by Bohdan Juříček
parent f0726ae443
commit 0ed2ee89a5
8 changed files with 216 additions and 109 deletions

View File

@@ -10,6 +10,7 @@ export type {
export type {
RefreshSuiteSyncKeys,
RefreshSuiteSyncKeysDep,
RefreshSuiteSyncKeysResult,
WriteModeRequiredForAllocationErrType,
} from './refreshSuiteSyncKeys';
export type { TurnOffSuiteSyncDep, TurnOffSuiteSync } from './turnOffSuiteSync';

View File

@@ -1,11 +1,10 @@
import { SuiteSyncOwner } from '@suite-common/suite-sync-storage';
import { DelegatedIdentityKey, TrezorDevice } from '@suite-common/suite-types';
import type { DeviceCancelledErrType, DeviceErrorType } from '@suite-common/suite-types';
import { TrezorDevice } from '@suite-common/suite-types';
import { Result } from '@trezor/type-utils';
type RefreshSuiteSyncKeysParams = {
device: TrezorDevice;
isWriteMode: boolean;
};
/**
@@ -22,15 +21,17 @@ export type WriteModeRequiredForAllocationErrType = {
type: 'WriteModeRequiredForAllocation';
};
export type RefreshSuiteSyncKeysResult = {
owner: SuiteSyncOwner;
delegatedKey: DelegatedIdentityKey;
};
export type RefreshSuiteSyncKeys = (
params: RefreshSuiteSyncKeysParams,
) => Promise<
Result<
SuiteSyncOwner,
| SuiteSyncUnavailableOnDeviceErrorType
| DeviceErrorType
| DeviceCancelledErrType
| WriteModeRequiredForAllocationErrType
RefreshSuiteSyncKeysResult,
SuiteSyncUnavailableOnDeviceErrorType | DeviceErrorType | DeviceCancelledErrType
>
>;

View File

@@ -2,23 +2,15 @@ import { Dispatch } from '@reduxjs/toolkit';
import { EnsureDelegatedIdentityKeyDep } from '@suite-common/delegated-identity-key-types';
import { isTrezorDeviceWithState } from '@suite-common/device';
import {
WriteModeRequiredForAllocation,
ensureDeviceHasQuotaThunk,
ensureOwnerHasAllocatedQuotaThunk,
} from '@suite-common/suite-sync-quota-manager';
import {
EnsureSuiteSyncOwnerDep,
RefreshSuiteSyncKeys,
SuiteSyncUnavailableOnDeviceErrorType,
} from '@suite-common/suite-sync-types';
import { notificationsActions } from '@suite-common/toast-notifications';
import { parseDeviceStaticSessionId } from '@suite-common/wallet-utils';
import { err, exhaustive, ok } from '@trezor/type-utils';
import { GetDeviceForStaticSessionIdDep } from './getDeviceForStaticSessionId';
import { GetDeviceHasAllowance } from './getDeviceHasAllowance';
import { LoadSuiteSyncOwnerFromStateDep } from './owner/createLoadSuiteSyncOwnerFromState';
/**
* Device is not connected or device is in a state/configuration, that does not
@@ -30,31 +22,19 @@ export const SuiteSyncUnavailableOnDeviceError = (): SuiteSyncUnavailableOnDevic
export type RefreshSuiteSyncKeysDeps = {
dispatch: Dispatch;
hasAllowance: GetDeviceHasAllowance;
} & EnsureSuiteSyncOwnerDep &
LoadSuiteSyncOwnerFromStateDep &
EnsureDelegatedIdentityKeyDep &
GetDeviceForStaticSessionIdDep;
export const createRefreshSuiteSync =
(deps: RefreshSuiteSyncKeysDeps): RefreshSuiteSyncKeys =>
async ({ device, isWriteMode }): ReturnType<RefreshSuiteSyncKeys> => {
async ({ device }): ReturnType<RefreshSuiteSyncKeys> => {
if (!device || !isTrezorDeviceWithState(device)) {
return err(SuiteSyncUnavailableOnDeviceError());
}
const deviceStaticId = device.state.staticSessionId;
const owner = await deps.loadSuiteSyncOwnerFromState({ deviceStaticId });
const { walletDescriptor } = parseDeviceStaticSessionId(device.state.staticSessionId);
// If device has an owner, is registered in quota manager and the owner
// already has an allowance, there's nothing to refresh — return success.
if (owner !== null && deps.hasAllowance({ walletDescriptor, deviceId: device.id })) {
return ok(owner);
}
if (
!device.connected || // disconnected device cannot resolve Evolu-Keys
device.mode !== 'normal' // bootloader
@@ -62,7 +42,7 @@ export const createRefreshSuiteSync =
return err(SuiteSyncUnavailableOnDeviceError());
}
const getKeys = async () => {
const getDelegatedIdentityKeys = async () => {
const delegatedKeyResult = await deps.ensureDelegatedIdentityKey({ device });
if (!delegatedKeyResult.success) {
@@ -76,13 +56,6 @@ export const createRefreshSuiteSync =
return err({ type: 'RefreshDeviceFailed' as const });
}
await deps.dispatch(
ensureDeviceHasQuotaThunk({
device: refreshedDevice,
delegatedKey: delegatedKeyResult.payload,
}),
);
const ownerResult = await deps.ensureSuiteSyncOwner({
device: refreshedDevice,
delegatedKey: delegatedKeyResult.payload,
@@ -92,26 +65,10 @@ export const createRefreshSuiteSync =
return ownerResult;
}
const allocatedQuota = await deps.dispatch(
ensureOwnerHasAllocatedQuotaThunk({
walletDescriptor,
ownerId: ownerResult.payload.ownerId,
delegatedKey: delegatedKeyResult.payload,
isWriteMode,
}),
);
if (
allocatedQuota.success === false &&
allocatedQuota.error.type === 'WriteModeRequiredForAllocation'
) {
return err(WriteModeRequiredForAllocation());
}
return ok(ownerResult.payload);
return ok({ owner: ownerResult.payload, delegatedKey: delegatedKeyResult.payload });
};
const result = await getKeys();
const result = await getDelegatedIdentityKeys();
if (!result.success) {
const errType = result.error.type;
@@ -119,7 +76,6 @@ export const createRefreshSuiteSync =
switch (errType) {
case 'DeviceError':
case 'DeviceCancelled':
case 'WriteModeRequiredForAllocation':
return err(result.error);
// Those errors are most likely due to Bug in the code or data corruption

View File

@@ -32,6 +32,7 @@ import {
import { createSaveSuiteSyncOwner } from './owner/createSaveSuiteSyncOwner';
import { createChangeRelayUrl } from './relay/createChangeRelayUrl';
import { DEFAULT_SUITE_SYNC_RELAY_URL } from './relay/relayUrl';
import { createEnsureQuota } from './storage/createEnsureQuota';
import { createEnsureStorage } from './storage/createEnsureStorage';
import { createEnsureWalletSuiteSyncOn } from './storage/createEnsureWalletSuiteSyncOn';
import { createEnsureWalletSuiteSyncOnWithErrorHandler } from './storage/createEnsureWalletSuiteSyncOnWithErrorHandler';
@@ -94,7 +95,11 @@ export const createSuiteSyncCompositionRoot = (
dispatch: deps.dispatch,
ensureDelegatedIdentityKey: deps.ensureDelegatedIdentityKey,
ensureSuiteSyncOwner,
loadSuiteSyncOwnerFromState,
getDeviceForStaticSessionId,
});
const ensureQuota = createEnsureQuota({
dispatch: deps.dispatch,
getDeviceForStaticSessionId,
hasAllowance: ({ walletDescriptor, deviceId }) =>
selectHasDeviceAllowance(deps.getState(), deviceId ?? null, walletDescriptor),
@@ -108,6 +113,7 @@ export const createSuiteSyncCompositionRoot = (
const ensureStorage = createEnsureStorage({
refreshSuiteSyncKeys,
ensureQuota,
suiteSyncStorageRepository,
createSuiteStorage,
defaultRelayUrl: DEFAULT_SUITE_SYNC_RELAY_URL,

View File

@@ -0,0 +1,78 @@
import { Dispatch } from '@reduxjs/toolkit';
import {
WriteModeRequiredForAllocation,
ensureDeviceHasQuotaThunk,
ensureOwnerHasAllocatedQuotaThunk,
} from '@suite-common/suite-sync-quota-manager';
import type { WriteModeRequiredForAllocationErrType } from '@suite-common/suite-sync-types';
import { DelegatedIdentityKey, SuiteSyncOwner } from '@suite-common/suite-types';
import { isTrezorDeviceWithState, parseDeviceStaticSessionId } from '@suite-common/wallet-utils';
import { StaticSessionId } from '@trezor/connect';
import { Result, err, ok } from '@trezor/type-utils';
import { GetDeviceForStaticSessionIdDep } from '../getDeviceForStaticSessionId';
import { GetDeviceHasAllowance } from '../getDeviceHasAllowance';
export type EnsureQuotaDeps = {
dispatch: Dispatch;
hasAllowance: GetDeviceHasAllowance;
} & GetDeviceForStaticSessionIdDep;
export type EnsureQuotaParams = {
deviceStaticSessionId: StaticSessionId;
delegatedKey: DelegatedIdentityKey;
owner: SuiteSyncOwner;
isWriteMode: boolean;
};
export type EnsureQuota = (
params: EnsureQuotaParams,
) => Promise<Result<void, WriteModeRequiredForAllocationErrType>>;
export type EnsureQuotaDep = {
ensureQuota: EnsureQuota;
};
export const createEnsureQuota =
(deps: EnsureQuotaDeps): EnsureQuota =>
async ({ deviceStaticSessionId, delegatedKey, owner, isWriteMode }) => {
const { walletDescriptor } = parseDeviceStaticSessionId(deviceStaticSessionId);
const device = deps.getDeviceForStaticSessionId(deviceStaticSessionId);
if (
device?.id !== null &&
device?.id !== undefined &&
deps.hasAllowance({ walletDescriptor, deviceId: device.id })
) {
return ok(undefined);
}
if (device !== null && isTrezorDeviceWithState(device)) {
await deps.dispatch(
ensureDeviceHasQuotaThunk({
device,
delegatedKey,
}),
);
}
const allocatedQuota = await deps.dispatch(
ensureOwnerHasAllocatedQuotaThunk({
walletDescriptor,
ownerId: owner.ownerId,
delegatedKey,
isWriteMode,
}),
);
if (
allocatedQuota.success === false &&
allocatedQuota.error.type === 'WriteModeRequiredForAllocation'
) {
return err(WriteModeRequiredForAllocation());
}
return ok(undefined);
};

View File

@@ -9,6 +9,7 @@ import { DeviceCancelledErrType, DeviceErrorType } from '@suite-common/suite-typ
import { StaticSessionId } from '@trezor/connect';
import { Result, err, ok } from '@trezor/type-utils';
import { EnsureQuotaDep } from './createEnsureQuota';
import { createStorageIdFromDeviceStaticSessionId } from './createStorageIdFromDeviceStaticSessionId';
import { SuiteSyncUnavailableOnDeviceError } from '../createRefreshSuiteSyncKeys';
import { GetDeviceForStaticSessionIdDep } from '../getDeviceForStaticSessionId';
@@ -19,7 +20,8 @@ export type EnsureStorageDeps = {
} & SuiteSyncStorageRepositoryDep &
CreateSuiteStorageDep &
RefreshSuiteSyncKeysDep &
GetDeviceForStaticSessionIdDep;
GetDeviceForStaticSessionIdDep &
EnsureQuotaDep;
export type EnsureStorageParams = {
deviceStaticSessionId: StaticSessionId;
@@ -59,15 +61,28 @@ export const createEnsureStorage =
return err(SuiteSyncUnavailableOnDeviceError());
}
const ownerResult = await deps.refreshSuiteSyncKeys({ device, isWriteMode });
const keysResult = await deps.refreshSuiteSyncKeys({ device });
if (!ownerResult.success) {
return ownerResult;
if (!keysResult.success) {
return keysResult;
}
const { owner, delegatedKey } = keysResult.payload;
const quotaResult = await deps.ensureQuota({
deviceStaticSessionId,
delegatedKey,
owner,
isWriteMode,
});
if (!quotaResult.success) {
return quotaResult;
}
const relayUrl = deps.getRelayUrl();
const newStorage = deps.createSuiteStorage({
suiteSyncOwner: ownerResult.payload,
suiteSyncOwner: owner,
relayUrl: relayUrl !== null && relayUrl.trim() !== '' ? relayUrl : deps.defaultRelayUrl,
});

View File

@@ -1,6 +1,7 @@
import { createMockDeps, mock } from '@suite-common/dependency-injection';
import {
SuiteSyncOwner,
asDelegatedIdentityKey,
asSuiteSyncOwnerId,
asSuiteSyncOwnerSecretHex,
} from '@suite-common/suite-sync-storage';
@@ -18,6 +19,8 @@ const OWNER_ABCD: SuiteSyncOwner = {
ownerSecret: asSuiteSyncOwnerSecretHex('owner-secret-abcd'),
};
const DELEGATED_KEY = asDelegatedIdentityKey('delegated-key-abcd');
const deviceStaticSessionId: StaticSessionId = '1@2:3';
describe(createEnsureStorage.name, () => {
@@ -34,6 +37,7 @@ describe(createEnsureStorage.name, () => {
},
createSuiteStorage: null,
refreshSuiteSyncKeys: null,
ensureQuota: null,
getDeviceForStaticSessionId: null,
});
@@ -60,6 +64,7 @@ describe(createEnsureStorage.name, () => {
},
createSuiteStorage: null,
refreshSuiteSyncKeys: null,
ensureQuota: null,
getDeviceForStaticSessionId: () => null,
});
@@ -89,6 +94,7 @@ describe(createEnsureStorage.name, () => {
},
createSuiteStorage: null,
refreshSuiteSyncKeys: () => Promise.resolve(refreshError),
ensureQuota: null,
getDeviceForStaticSessionId: () => device,
});
@@ -99,7 +105,67 @@ describe(createEnsureStorage.name, () => {
expect(result).toBe(refreshError);
expect(deps.getDeviceForStaticSessionId).toHaveBeenCalledWith(deviceStaticSessionId);
expect(deps.refreshSuiteSyncKeys).toHaveBeenCalledWith({ device, isWriteMode: false });
expect(deps.refreshSuiteSyncKeys).toHaveBeenCalledWith({ device });
expect(deps.createSuiteStorage).not.toHaveBeenCalled();
});
it('calls ensureQuota with correct params after refreshSuiteSyncKeys succeeds', async () => {
const device = mockSuiteDevice();
const newStorage = createSuiteSyncStorageMock();
const deps = createMockDeps<EnsureStorageDeps>({
defaultRelayUrl: 'wss://default-relay.example.com',
getRelayUrl: () => null,
suiteSyncStorageRepository: {
get: () => null,
set: mock(() => {}),
delete: null,
},
createSuiteStorage: () => newStorage,
refreshSuiteSyncKeys: () =>
Promise.resolve(ok({ owner: OWNER_ABCD, delegatedKey: DELEGATED_KEY })),
ensureQuota: () => Promise.resolve(ok(undefined)),
getDeviceForStaticSessionId: () => device,
});
await createEnsureStorage(deps)({
deviceStaticSessionId,
isWriteMode: false,
});
expect(deps.ensureQuota).toHaveBeenCalledWith({
deviceStaticSessionId,
delegatedKey: DELEGATED_KEY,
owner: OWNER_ABCD,
isWriteMode: false,
});
});
it('returns error when ensureQuota fails', async () => {
const device = mockSuiteDevice();
const quotaError = err({ type: 'WriteModeRequiredForAllocation' as const });
const deps = createMockDeps<EnsureStorageDeps>({
defaultRelayUrl: 'wss://default-relay.example.com',
getRelayUrl: () => null,
suiteSyncStorageRepository: {
get: () => null,
set: null,
delete: null,
},
createSuiteStorage: null,
refreshSuiteSyncKeys: () =>
Promise.resolve(ok({ owner: OWNER_ABCD, delegatedKey: DELEGATED_KEY })),
ensureQuota: () => Promise.resolve(quotaError),
getDeviceForStaticSessionId: () => device,
});
const result = await createEnsureStorage(deps)({
deviceStaticSessionId,
isWriteMode: true,
});
expect(result).toBe(quotaError);
expect(deps.createSuiteStorage).not.toHaveBeenCalled();
});
@@ -116,7 +182,9 @@ describe(createEnsureStorage.name, () => {
delete: null,
},
createSuiteStorage: () => newStorage,
refreshSuiteSyncKeys: () => Promise.resolve(ok(OWNER_ABCD)),
refreshSuiteSyncKeys: () =>
Promise.resolve(ok({ owner: OWNER_ABCD, delegatedKey: DELEGATED_KEY })),
ensureQuota: () => Promise.resolve(ok(undefined)),
getDeviceForStaticSessionId: () => device,
});
@@ -127,7 +195,7 @@ describe(createEnsureStorage.name, () => {
expect(result).toEqual(ok(newStorage));
expect(deps.getDeviceForStaticSessionId).toHaveBeenCalledWith(deviceStaticSessionId);
expect(deps.refreshSuiteSyncKeys).toHaveBeenCalledWith({ device, isWriteMode: false });
expect(deps.refreshSuiteSyncKeys).toHaveBeenCalledWith({ device });
expect(deps.createSuiteStorage).toHaveBeenCalledWith({
suiteSyncOwner: OWNER_ABCD,
relayUrl: 'wss://default-relay.example.com',
@@ -150,7 +218,9 @@ describe(createEnsureStorage.name, () => {
delete: null,
},
createSuiteStorage: () => newStorage,
refreshSuiteSyncKeys: () => Promise.resolve(ok(OWNER_ABCD)),
refreshSuiteSyncKeys: () =>
Promise.resolve(ok({ owner: OWNER_ABCD, delegatedKey: DELEGATED_KEY })),
ensureQuota: () => Promise.resolve(ok(undefined)),
getDeviceForStaticSessionId: () => device,
});
@@ -178,7 +248,9 @@ describe(createEnsureStorage.name, () => {
delete: null,
},
createSuiteStorage: () => newStorage,
refreshSuiteSyncKeys: () => Promise.resolve(ok(OWNER_ABCD)),
refreshSuiteSyncKeys: () =>
Promise.resolve(ok({ owner: OWNER_ABCD, delegatedKey: DELEGATED_KEY })),
ensureQuota: () => Promise.resolve(ok(undefined)),
getDeviceForStaticSessionId: () => device,
});
@@ -208,7 +280,9 @@ describe(createEnsureStorage.name, () => {
delete: null,
},
createSuiteStorage: () => newStorage,
refreshSuiteSyncKeys: () => Promise.resolve(ok(OWNER_ABCD)),
refreshSuiteSyncKeys: () =>
Promise.resolve(ok({ owner: OWNER_ABCD, delegatedKey: DELEGATED_KEY })),
ensureQuota: () => Promise.resolve(ok(undefined)),
getDeviceForStaticSessionId: () => device,
});

View File

@@ -9,16 +9,11 @@ import { ok } from '@trezor/type-utils';
import { RefreshSuiteSyncKeysDeps, createRefreshSuiteSync } from '../createRefreshSuiteSyncKeys';
import { GetDeviceForStaticSessionId } from '../getDeviceForStaticSessionId';
import { LoadSuiteSyncOwnerFromState } from '../owner/createLoadSuiteSyncOwnerFromState';
const createMockDeps = () =>
({
dispatch: mockNotExpected<Dispatch>('dispatch'),
hasAllowance: mockNotExpected<RefreshSuiteSyncKeysDeps['hasAllowance']>('hasAllowance'),
ensureSuiteSyncOwner: mockNotExpected<EnsureSuiteSyncOwner>('ensureSuiteSyncOwner'),
loadSuiteSyncOwnerFromState: mockNotExpected<LoadSuiteSyncOwnerFromState>(
'loadSuiteSyncOwnerFromState',
),
ensureDelegatedIdentityKey: mockNotExpected<EnsureDelegatedIdentityKey>(
'ensureDelegatedIdentityKey',
),
@@ -61,46 +56,27 @@ describe(createRefreshSuiteSync.name, () => {
const refreshSuiteSyncKeys = createRefreshSuiteSync(deps);
const result = await refreshSuiteSyncKeys({
device: createDevice({}, null),
isWriteMode: false,
});
expect(result.success).toEqual(false);
expect(!result.success && result.error.type).toBe('SuiteSyncUnavailableOnDeviceError');
});
it('returns an suite sync owner when device has state and is available', async () => {
it('fails to get keys when device is disconnected', async () => {
const deps = createMockDeps();
deps.loadSuiteSyncOwnerFromState.mockResolvedValue(OWNER_1);
deps.hasAllowance.mockReturnValue(true);
const refreshSuiteSyncKeys = createRefreshSuiteSync(deps);
const result = await refreshSuiteSyncKeys({
device: createDevice(),
isWriteMode: false,
});
expect(result.success).toEqual(true);
expect(result.success && result.payload).toEqual(OWNER_1);
});
it('fails to get keys, when device is disconnected', async () => {
const deps = createMockDeps();
deps.loadSuiteSyncOwnerFromState.mockResolvedValue(null);
const refreshSuiteSyncKeys = createRefreshSuiteSync(deps);
const result = await refreshSuiteSyncKeys({
device: createDevice({ connected: false }),
isWriteMode: false,
});
expect(result.success).toEqual(false);
expect(!result.success && result.error.type).toEqual('SuiteSyncUnavailableOnDeviceError');
});
it('ensures that the delegated identity key is available when owner is not in state', async () => {
it('ensures that the delegated identity key is available', async () => {
const deps = createMockDeps();
deps.dispatch.mockImplementation(() => Promise.resolve(ok()));
deps.loadSuiteSyncOwnerFromState.mockResolvedValue(null);
deps.ensureSuiteSyncOwner.mockResolvedValue(ok(OWNER_1));
deps.ensureDelegatedIdentityKey.mockResolvedValue(
ok(asDelegatedIdentityKey('delegated-key-value')),
@@ -110,25 +86,23 @@ describe(createRefreshSuiteSync.name, () => {
deps.getDeviceForStaticSessionId.mockImplementation(() => mockDevice);
const refreshSuiteSyncKeys = createRefreshSuiteSync(deps);
await refreshSuiteSyncKeys({
const result = await refreshSuiteSyncKeys({
device: mockDevice,
isWriteMode: false,
});
expect(deps.ensureDelegatedIdentityKey).toHaveBeenCalledWith({
device: mockDevice,
});
expect(result.success).toBe(true);
expect(result.success && result.payload).toEqual({
owner: OWNER_1,
delegatedKey: asDelegatedIdentityKey('delegated-key-value'),
});
});
it('requests ensureSuiteSyncOwner when owner is not in state', async () => {
it('requests ensureSuiteSyncOwner', async () => {
const deps = createMockDeps();
deps.dispatch.mockImplementation(() =>
Promise.resolve({
success: false,
error: { type: 'WriteModeRequiredForAllocation' },
}),
);
deps.loadSuiteSyncOwnerFromState.mockResolvedValue(null);
deps.dispatch.mockImplementation(() => Promise.resolve(ok()));
deps.ensureSuiteSyncOwner.mockResolvedValue(ok(OWNER_1));
deps.ensureDelegatedIdentityKey.mockResolvedValue(
ok(asDelegatedIdentityKey('delegated-key-value')),
@@ -140,7 +114,6 @@ describe(createRefreshSuiteSync.name, () => {
const refreshSuiteSyncKeys = createRefreshSuiteSync(deps);
await refreshSuiteSyncKeys({
device: mockDevice,
isWriteMode: false,
});
expect(deps.ensureSuiteSyncOwner).toHaveBeenCalledWith({
@@ -149,10 +122,9 @@ describe(createRefreshSuiteSync.name, () => {
});
});
it('finally returns the new refreshed owner', async () => {
it('returns owner and delegatedKey on success', async () => {
const deps = createMockDeps();
deps.dispatch.mockImplementation(() => Promise.resolve(ok()));
deps.loadSuiteSyncOwnerFromState.mockResolvedValue(null);
deps.ensureDelegatedIdentityKey.mockResolvedValue(
ok(asDelegatedIdentityKey('delegated-key-value')),
);
@@ -169,11 +141,15 @@ describe(createRefreshSuiteSync.name, () => {
const refreshSuiteSyncKeys = createRefreshSuiteSync(deps);
const result = await refreshSuiteSyncKeys({
device: mockDevice,
isWriteMode: false,
});
expect(result.success).toBe(true);
expect(result.success && result.payload.ownerId).toEqual('new-owner-id');
expect(result.success && result.payload.ownerSecret).toEqual('new-secret-public-key');
expect(result.success && result.payload).toEqual({
owner: {
ownerId: asSuiteSyncOwnerId('new-owner-id'),
ownerSecret: asSuiteSyncOwnerSecretHex('new-secret-public-key'),
},
delegatedKey: asDelegatedIdentityKey('delegated-key-value'),
});
});
});