PermissionedDomain XLS-80d (#2874)

This commit is contained in:
Chenna Keshava B S
2025-02-07 14:44:53 -08:00
committed by GitHub
parent ce5ca316ca
commit 189abc1a26
23 changed files with 496 additions and 14 deletions

View File

@@ -189,3 +189,4 @@ fixInnerObjTemplate2
fixEnforceNFTokenTrustline
fixReducedOffersV2
DynamicNFT
PermissionedDomains

View File

@@ -64,7 +64,7 @@ From the top-level xrpl.js folder (one level above `packages`), run the followin
```bash
npm install
# sets up the rippled standalone Docker container - you can skip this step if you already have it set up
docker run -p 6006:6006 --rm -it --name rippled_standalone --volume $PWD/.ci-config:/etc/opt/ripple/ --entrypoint bash rippleci/rippled:2.3.0-rc1 -c 'rippled -a'
docker run -p 6006:6006 --rm -it --name rippled_standalone --volume $PWD/.ci-config:/etc/opt/ripple/ --entrypoint bash rippleci/rippled:develop -c 'rippled -a'
npm run build
npm run test:integration
```
@@ -76,7 +76,7 @@ Breaking down the command:
`--name rippled_standalone` is an instance name for clarity
* `--volume $PWD/.ci-config:/etc/opt/ripple/` identifies the `rippled.cfg` and `validators.txt` to import. It must be an absolute path, so we use `$PWD` instead of `./`.
* `rippleci/rippled` is an image that is regularly updated with the latest `rippled` releases
* `--entrypoint bash rippleci/rippled:2.3.0-rc1` manually overrides the entrypoint (for versions of rippled >= 2.3.0)
* `--entrypoint bash rippleci/rippled:develop` manually overrides the entrypoint (for the latest version of rippled on the `develop` branch)
* `-c 'rippled -a'` provides the bash command to start `rippled` in standalone mode from the manual entrypoint
### Browser Tests
@@ -92,7 +92,7 @@ This should be run from the `xrpl.js` top level folder (one above the `packages`
```bash
npm run build
# sets up the rippled standalone Docker container - you can skip this step if you already have it set up
docker run -p 6006:6006 --rm -it --name rippled_standalone --volume $PWD/.ci-config:/etc/opt/ripple/ --entrypoint bash rippleci/rippled:2.3.0-rc1 -c 'rippled -a'
docker run -p 6006:6006 --rm -it --name rippled_standalone --volume $PWD/.ci-config:/etc/opt/ripple/ --entrypoint bash rippleci/rippled:develop -c 'rippled -a'
npm run test:browser
```

View File

@@ -1250,6 +1250,16 @@
"type": "Hash256"
}
],
[
"DomainID",
{
"nth": 34,
"isVLEncoded": false,
"isSerialized": true,
"isSigningField": true,
"type": "Hash256"
}
],
[
"hash",
{
@@ -2530,6 +2540,15 @@
"type": "STArray"
}
],
[
"AcceptedCredentials", {
"nth": 28,
"isVLEncoded": false,
"isSerialized": true,
"isSigningField": true,
"type": "STArray"
}
],
[
"CloseResolution",
{
@@ -2863,6 +2882,7 @@
"Oracle": 128,
"Credential": 129,
"PayChannel": 120,
"PermissionedDomain": 130,
"RippleState": 114,
"SignerList": 83,
"Ticket": 84,
@@ -3094,6 +3114,8 @@
"PaymentChannelClaim": 15,
"PaymentChannelCreate": 13,
"PaymentChannelFund": 14,
"PermissionedDomainSet": 62,
"PermissionedDomainDelete": 63,
"SetFee": 101,
"SetRegularKey": 5,
"SignerListSet": 12,

View File

@@ -6,12 +6,15 @@ Subscribe to [the **xrpl-announce** mailing list](https://groups.google.com/g/xr
### Added
* Adds utility function `convertTxFlagsToNumber`
* Implementation of XLS-80d PermissionedDomain feature.
* Support for the `simulate` RPC ([XLS-69](https://github.com/XRPLF/XRPL-Standards/tree/master/XLS-0069-simulate))
### Changed
* Deprecated `setTransactionFlagsToNumber`. Start using convertTxFlagsToNumber instead
### Added
* Support for the `simulate` RPC ([XLS-69](https://github.com/XRPLF/XRPL-Standards/tree/master/XLS-0069-simulate))
### Fixed
* Include `network_id` field in the `server_state` response interface.
## 4.1.0 (2024-12-23)

View File

@@ -13,6 +13,7 @@ import NegativeUNL from './NegativeUNL'
import Offer from './Offer'
import Oracle from './Oracle'
import PayChannel from './PayChannel'
import PermissionedDomain from './PermissionedDomain'
import RippleState from './RippleState'
import SignerList from './SignerList'
import Ticket from './Ticket'
@@ -35,6 +36,7 @@ type LedgerEntry =
| Offer
| Oracle
| PayChannel
| PermissionedDomain
| RippleState
| SignerList
| Ticket
@@ -61,6 +63,7 @@ type LedgerEntryFilter =
| 'offer'
| 'oracle'
| 'payment_channel'
| 'permissioned_domain'
| 'signer_list'
| 'state'
| 'ticket'

View File

@@ -0,0 +1,29 @@
import { AuthorizeCredential } from '../common'
import { BaseLedgerEntry, HasPreviousTxnID } from './BaseLedgerEntry'
export default interface PermissionedDomain
extends BaseLedgerEntry,
HasPreviousTxnID {
/* The ledger object's type (PermissionedDomain). */
LedgerEntryType: 'PermissionedDomain'
/* The account that controls the settings of the domain. */
Owner: string
/* The credentials that are accepted by the domain.
Ownership of one of these credentials automatically
makes you a member of the domain. */
AcceptedCredentials: AuthorizeCredential[]
/* Flag values associated with this object. */
Flags: 0
/* Owner account's directory page containing the PermissionedDomain object. */
OwnerNode: string
/* The Sequence value of the PermissionedDomainSet
transaction that created this domain. Used in combination
with the Account to identify this domain. */
Sequence: number
}

View File

@@ -51,6 +51,7 @@ export interface ServerStateResponse extends BaseResponse {
load_factor_fee_queue?: number
load_factor_fee_reference?: number
load_factor_server?: number
network_id: number
peer_disconnects?: string
peer_disconnects_resources?: string
peers: number

View File

@@ -7,6 +7,7 @@ import {
validateCredentialsList,
validateOptionalField,
validateRequiredField,
MAX_AUTHORIZED_CREDENTIALS,
} from './common'
/**
@@ -54,5 +55,6 @@ export function validateAccountDelete(tx: Record<string, unknown>): void {
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- known from base check
tx.TransactionType as string,
true,
MAX_AUTHORIZED_CREDENTIALS,
)
}

View File

@@ -9,15 +9,15 @@ import {
AuthorizeCredential,
Currency,
IssuedCurrencyAmount,
MPTAmount,
Memo,
Signer,
XChainBridge,
MPTAmount,
} from '../common'
import { onlyHasFields } from '../utils'
const MEMO_SIZE = 3
const MAX_CREDENTIALS_LIST_LENGTH = 8
export const MAX_AUTHORIZED_CREDENTIALS = 8
const MAX_CREDENTIAL_BYTE_LENGTH = 64
const MAX_CREDENTIAL_TYPE_LENGTH = MAX_CREDENTIAL_BYTE_LENGTH * 2
@@ -134,7 +134,9 @@ export function isIssuedCurrency(
* @param input - The input to check the form and type of
* @returns Whether the AuthorizeCredential is properly formed
*/
function isAuthorizeCredential(input: unknown): input is AuthorizeCredential {
export function isAuthorizeCredential(
input: unknown,
): input is AuthorizeCredential {
return (
isRecord(input) &&
isRecord(input.Credential) &&
@@ -455,13 +457,16 @@ export function validateCredentialType(tx: Record<string, unknown>): void {
* @param credentials An array of credential IDs to check for errors
* @param transactionType The transaction type to include in error messages
* @param isStringID Toggle for if array contains IDs instead of AuthorizeCredential objects
* @param maxCredentials The maximum length of the credentials array.
* PermissionedDomainSet transaction uses 10, other transactions use 8.
* @throws Validation Error if the formatting is incorrect
*/
// eslint-disable-next-line max-lines-per-function -- separating logic further will add unnecessary complexity
// eslint-disable-next-line max-lines-per-function, max-params -- separating logic further will add unnecessary complexity
export function validateCredentialsList(
credentials: unknown,
transactionType: string,
isStringID: boolean,
maxCredentials: number,
): void {
if (credentials == null) {
return
@@ -471,9 +476,9 @@ export function validateCredentialsList(
`${transactionType}: Credentials must be an array`,
)
}
if (credentials.length > MAX_CREDENTIALS_LIST_LENGTH) {
if (credentials.length > maxCredentials) {
throw new ValidationError(
`${transactionType}: Credentials length cannot exceed ${MAX_CREDENTIALS_LIST_LENGTH} elements`,
`${transactionType}: Credentials length cannot exceed ${maxCredentials} elements`,
)
} else if (credentials.length === 0) {
throw new ValidationError(
@@ -500,7 +505,42 @@ export function validateCredentialsList(
}
}
function containsDuplicates(objectList: object[]): boolean {
const objSet = new Set(objectList.map((obj) => JSON.stringify(obj)))
return objSet.size !== objectList.length
// Type guard to ensure we're working with AuthorizeCredential[]
// Note: This is not a rigorous type-guard. A more thorough solution would be to iterate over the array and check each item.
function isAuthorizeCredentialArray(
list: AuthorizeCredential[] | string[],
): list is AuthorizeCredential[] {
return typeof list[0] !== 'string'
}
/**
* Check if an array of objects contains any duplicates.
*
* @param objectList - Array of objects to check for duplicates
* @returns True if duplicates exist, false otherwise
*/
export function containsDuplicates(
objectList: AuthorizeCredential[] | string[],
): boolean {
// Case-1: Process a list of string-IDs
if (typeof objectList[0] === 'string') {
const objSet = new Set(objectList.map((obj) => JSON.stringify(obj)))
return objSet.size !== objectList.length
}
// Case-2: Process a list of nested objects
const seen = new Set<string>()
if (isAuthorizeCredentialArray(objectList)) {
for (const item of objectList) {
const key = `${item.Credential.Issuer}-${item.Credential.CredentialType}`
// eslint-disable-next-line max-depth -- necessary to check for type-guards
if (seen.has(key)) {
return true
}
seen.add(key)
}
}
return false
}

View File

@@ -5,6 +5,7 @@ import {
BaseTransaction,
validateBaseTransaction,
validateCredentialsList,
MAX_AUTHORIZED_CREDENTIALS,
} from './common'
/**
@@ -72,6 +73,7 @@ export function validateDepositPreauth(tx: Record<string, unknown>): void {
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- confirmed in base transaction check
tx.TransactionType as string,
false,
MAX_AUTHORIZED_CREDENTIALS,
)
} else if (tx.UnauthorizeCredentials !== undefined) {
validateCredentialsList(
@@ -79,6 +81,7 @@ export function validateDepositPreauth(tx: Record<string, unknown>): void {
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- confirmed in base transaction check
tx.TransactionType as string,
false,
MAX_AUTHORIZED_CREDENTIALS,
)
}
}

View File

@@ -7,6 +7,7 @@ import {
validateBaseTransaction,
validateCredentialsList,
validateRequiredField,
MAX_AUTHORIZED_CREDENTIALS,
} from './common'
/**
@@ -55,6 +56,7 @@ export function validateEscrowFinish(tx: Record<string, unknown>): void {
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- known from base check
tx.TransactionType as string,
true,
MAX_AUTHORIZED_CREDENTIALS,
)
if (tx.OfferSequence == null) {

View File

@@ -106,3 +106,6 @@ export {
XChainModifyBridgeFlags,
XChainModifyBridgeFlagsInterface,
} from './XChainModifyBridge'
export { PermissionedDomainSet } from './permissionedDomainSet'
export { PermissionedDomainDelete } from './permissionedDomainDelete'

View File

@@ -13,6 +13,7 @@ import {
isNumber,
Account,
validateCredentialsList,
MAX_AUTHORIZED_CREDENTIALS,
} from './common'
import type { TransactionMetadataBase } from './metadata'
@@ -188,6 +189,7 @@ export function validatePayment(tx: Record<string, unknown>): void {
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- known from base check
tx.TransactionType as string,
true,
MAX_AUTHORIZED_CREDENTIALS,
)
if (tx.InvoiceID !== undefined && typeof tx.InvoiceID !== 'string') {

View File

@@ -5,6 +5,7 @@ import {
GlobalFlags,
validateBaseTransaction,
validateCredentialsList,
MAX_AUTHORIZED_CREDENTIALS,
} from './common'
/**
@@ -153,6 +154,7 @@ export function validatePaymentChannelClaim(tx: Record<string, unknown>): void {
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- known from base check
tx.TransactionType as string,
true,
MAX_AUTHORIZED_CREDENTIALS,
)
if (tx.Channel === undefined) {

View File

@@ -0,0 +1,28 @@
import {
BaseTransaction,
isString,
validateBaseTransaction,
validateRequiredField,
} from './common'
export interface PermissionedDomainDelete extends BaseTransaction {
/* The transaction type (PermissionedDomainDelete). */
TransactionType: 'PermissionedDomainDelete'
/* The domain to delete. */
DomainID: string
}
/**
* Verify the form and type of a PermissionedDomainDelete transaction.
*
* @param tx - The transaction to verify.
* @throws When the transaction is malformed.
*/
export function validatePermissionedDomainDelete(
tx: Record<string, unknown>,
): void {
validateBaseTransaction(tx)
validateRequiredField(tx, 'DomainID', isString)
}

View File

@@ -0,0 +1,54 @@
import { AuthorizeCredential } from '../common'
import {
BaseTransaction,
isString,
validateBaseTransaction,
validateOptionalField,
validateRequiredField,
validateCredentialsList,
} from './common'
const MAX_ACCEPTED_CREDENTIALS = 10
export interface PermissionedDomainSet extends BaseTransaction {
/* The transaction type (PermissionedDomainSet). */
TransactionType: 'PermissionedDomainSet'
/* The domain to modify. Must be included if modifying an existing domain. */
DomainID?: string
/* The credentials that are accepted by the domain. Ownership of one
of these credentials automatically makes you a member of the domain.
An empty array means deleting the field. */
AcceptedCredentials: AuthorizeCredential[]
}
/**
* Validate a PermissionedDomainSet transaction.
*
* @param tx - The transaction to validate.
* @throws {ValidationError} When the transaction is invalid.
*/
export function validatePermissionedDomainSet(
tx: Record<string, unknown>,
): void {
validateBaseTransaction(tx)
validateOptionalField(tx, 'DomainID', isString)
validateRequiredField(
tx,
'AcceptedCredentials',
() => tx.AcceptedCredentials instanceof Array,
)
validateCredentialsList(
tx.AcceptedCredentials,
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- known from base check
tx.TransactionType as string,
// PermissionedDomainSet uses AuthorizeCredential nested objects only, strings are not allowed
false,
// PermissionedDomainSet uses at most 10 accepted credentials. This is different from Credential-feature transactions.
MAX_ACCEPTED_CREDENTIALS,
)
}

View File

@@ -75,6 +75,14 @@ import {
PaymentChannelFund,
validatePaymentChannelFund,
} from './paymentChannelFund'
import {
PermissionedDomainDelete,
validatePermissionedDomainDelete,
} from './permissionedDomainDelete'
import {
PermissionedDomainSet,
validatePermissionedDomainSet,
} from './permissionedDomainSet'
import { SetFee } from './setFee'
import { SetRegularKey, validateSetRegularKey } from './setRegularKey'
import { SignerListSet, validateSignerListSet } from './signerListSet'
@@ -153,6 +161,8 @@ export type SubmittableTransaction =
| PaymentChannelClaim
| PaymentChannelCreate
| PaymentChannelFund
| PermissionedDomainSet
| PermissionedDomainDelete
| SetRegularKey
| SignerListSet
| TicketCreate
@@ -415,6 +425,14 @@ export function validate(transaction: Record<string, unknown>): void {
validatePaymentChannelFund(tx)
break
case 'PermissionedDomainSet':
validatePermissionedDomainSet(tx)
break
case 'PermissionedDomainDelete':
validatePermissionedDomainDelete(tx)
break
case 'SetRegularKey':
validateSetRegularKey(tx)
break

View File

@@ -125,6 +125,7 @@ describe('server_info (rippled)', function () {
'build_version',
'node_size',
'initial_sync_duration_us',
'network_id',
'git',
]
assert.deepEqual(

View File

@@ -60,6 +60,7 @@ describe('server_state', function () {
load_factor_fee_queue: 256,
load_factor_fee_reference: 256,
load_factor_server: 256,
network_id: 63456,
peer_disconnects: '0',
peer_disconnects_resources: '0',
peers: 0,
@@ -117,6 +118,7 @@ describe('server_state', function () {
'initial_sync_duration_us',
'ports',
'git',
'network_id',
]
assert.deepEqual(
omit(response.result.state, removeKeys),

View File

@@ -0,0 +1,86 @@
import { stringToHex } from '@xrplf/isomorphic/utils'
import { assert } from 'chai'
import {
LedgerEntryRequest,
PermissionedDomainDelete,
PermissionedDomainSet,
AuthorizeCredential,
} from '../../../src'
import PermissionedDomain from '../../../src/models/ledger/PermissionedDomain'
import serverUrl from '../serverUrl'
import {
setupClient,
teardownClient,
type XrplIntegrationTestContext,
} from '../setup'
import { testTransaction } from '../utils'
// how long before each test case times out
const TIMEOUT = 20000
describe('PermissionedDomainSet', function () {
let testContext: XrplIntegrationTestContext
beforeEach(async () => {
testContext = await setupClient(serverUrl)
})
afterEach(async () => teardownClient(testContext))
it(
'Lifecycle of PermissionedDomain ledger object',
async () => {
const sampleCredential: AuthorizeCredential = {
Credential: {
CredentialType: stringToHex('Passport'),
Issuer: testContext.wallet.classicAddress,
},
}
// Step-1: Test the PermissionedDomainSet transaction
const pdSet: PermissionedDomainSet = {
TransactionType: 'PermissionedDomainSet',
Account: testContext.wallet.classicAddress,
AcceptedCredentials: [sampleCredential],
}
await testTransaction(testContext.client, pdSet, testContext.wallet)
// Step-2: Validate the ledger_entry, account_objects RPC methods
// validate the account_objects RPC
const result = await testContext.client.request({
command: 'account_objects',
account: testContext.wallet.classicAddress,
type: 'permissioned_domain',
})
assert.equal(result.result.account_objects.length, 1)
const pd = result.result.account_objects[0] as PermissionedDomain
assert.equal(pd.Flags, 0)
expect(pd.AcceptedCredentials).toEqual([sampleCredential])
// validate the ledger_entry RPC
const ledgerEntryRequest: LedgerEntryRequest = {
command: 'ledger_entry',
// fetch the PD `index` from the previous account_objects RPC response
index: pd.index,
}
const ledgerEntryResult = await testContext.client.request(
ledgerEntryRequest,
)
assert.deepEqual(pd, ledgerEntryResult.result.node)
// Step-3: Test the PDDelete transaction
const pdDelete: PermissionedDomainDelete = {
TransactionType: 'PermissionedDomainDelete',
Account: testContext.wallet.classicAddress,
// fetch the PD `index` from the previous account_objects RPC response
DomainID: pd.index,
}
await testTransaction(testContext.client, pdDelete, testContext.wallet)
},
TIMEOUT,
)
})

View File

@@ -0,0 +1,49 @@
import { assert } from 'chai'
import { validate, ValidationError } from '../../src'
import { validatePermissionedDomainDelete } from '../../src/models/transactions/permissionedDomainDelete'
/**
* PermissionedDomainDelete Transaction Verification Testing.
*
* Providing runtime verification testing for each specific transaction type.
*/
describe('PermissionedDomainDelete', function () {
let tx
beforeEach(function () {
tx = {
TransactionType: 'PermissionedDomainDelete',
Account: 'rfmDuhDyLGgx94qiwf3YF8BUV5j6KSvE8',
DomainID:
'D88930B33C2B6831660BFD006D91FF100011AD4E67CBB78B460AF0A215103737',
} as any
})
it('verifies valid PermissionedDomainDelete', function () {
assert.doesNotThrow(() => validatePermissionedDomainDelete(tx))
assert.doesNotThrow(() => validate(tx))
})
it(`throws w/ missing field DomainID`, function () {
delete tx.DomainID
const errorMessage = 'PermissionedDomainDelete: missing field DomainID'
assert.throws(
() => validatePermissionedDomainDelete(tx),
ValidationError,
errorMessage,
)
assert.throws(() => validate(tx), ValidationError, errorMessage)
})
it(`throws w/ invalid DomainID`, function () {
tx.DomainID = 1234
const errorMessage = 'PermissionedDomainDelete: invalid field DomainID'
assert.throws(
() => validatePermissionedDomainDelete(tx),
ValidationError,
errorMessage,
)
assert.throws(() => validate(tx), ValidationError, errorMessage)
})
})

View File

@@ -0,0 +1,92 @@
import { stringToHex } from '@xrplf/isomorphic/dist/utils'
import { assert } from 'chai'
import { AuthorizeCredential, validate, ValidationError } from '../../src'
/**
* PermissionedDomainSet Transaction Verification Testing.
*
* Providing runtime verification testing for each specific transaction type.
*/
describe('PermissionedDomainSet', function () {
let tx
const sampleCredential: AuthorizeCredential = {
Credential: {
CredentialType: stringToHex('Passport'),
Issuer: 'rfmDuhDyLGgx94qiwf3YF8BUV5j6KSvE8',
},
}
beforeEach(function () {
tx = {
TransactionType: 'PermissionedDomainSet',
Account: 'rfmDuhDyLGgx94qiwf3YF8BUV5j6KSvE8',
DomainID:
'D88930B33C2B6831660BFD006D91FF100011AD4E67CBB78B460AF0A215103737',
AcceptedCredentials: [sampleCredential],
} as any
})
it('verifies valid PermissionedDomainSet', function () {
assert.doesNotThrow(() => validate(tx))
})
it(`throws with invalid field DomainID`, function () {
// DomainID is expected to be a string
tx.DomainID = 1234
const errorMessage = 'PermissionedDomainSet: invalid field DomainID'
assert.throws(() => validate(tx), ValidationError, errorMessage)
})
it(`throws w/ missing field AcceptedCredentials`, function () {
delete tx.AcceptedCredentials
const errorMessage =
'PermissionedDomainSet: missing field AcceptedCredentials'
assert.throws(() => validate(tx), ValidationError, errorMessage)
})
it('throws when AcceptedCredentials exceeds maximum length', function () {
tx.AcceptedCredentials = Array(11).fill(sampleCredential)
assert.throws(
() => validate(tx),
ValidationError,
'PermissionedDomainSet: Credentials length cannot exceed 10 elements',
)
})
it('throws when AcceptedCredentials is empty', function () {
tx.AcceptedCredentials = []
assert.throws(
() => validate(tx),
ValidationError,
'PermissionedDomainSet: Credentials cannot be an empty array',
)
})
it('throws when AcceptedCredentials is not an array type', function () {
tx.AcceptedCredentials = 'AcceptedCredentials is not an array'
assert.throws(
() => validate(tx),
ValidationError,
'PermissionedDomainSet: invalid field AcceptedCredentials',
)
})
it('throws when AcceptedCredentials contains duplicates', function () {
tx.AcceptedCredentials = [sampleCredential, sampleCredential]
assert.throws(
() => validate(tx),
ValidationError,
'PermissionedDomainSet: Credentials cannot contain duplicate elements',
)
})
it('throws when AcceptedCredentials contains invalid format', function () {
tx.AcceptedCredentials = [{ Field1: 'Value1', Field2: 'Value2' }]
assert.throws(
() => validate(tx),
ValidationError,
'PermissionedDomainSet: Invalid Credentials format',
)
})
})

View File

@@ -13,7 +13,9 @@ import {
TrustSet,
TrustSetFlags,
} from '../../src'
import { AuthorizeCredential } from '../../src/models/common'
import { AccountRootFlags } from '../../src/models/ledger'
import { containsDuplicates } from '../../src/models/transactions/common'
import { isFlagEnabled } from '../../src/models/utils'
import {
setTransactionFlagsToNumber,
@@ -28,6 +30,43 @@ import {
* Provides tests for utils used in models.
*/
describe('Models Utils', function () {
describe('validate containsDuplicates utility method', function () {
it(`use nested-objects for input parameters, list contains duplicates`, function () {
// change the order of the inner-objects in the list
const list_with_duplicates: AuthorizeCredential[] = [
{ Credential: { Issuer: 'alice', CredentialType: 'Passport' } },
{ Credential: { CredentialType: 'Passport', Issuer: 'alice' } },
]
assert.isTrue(containsDuplicates(list_with_duplicates))
})
it(`use nested-objects for input parameters, no duplicates`, function () {
const list_without_dups: AuthorizeCredential[] = [
{ Credential: { Issuer: 'alice', CredentialType: 'Passport' } },
{ Credential: { CredentialType: 'DMV_license', Issuer: 'bob' } },
]
assert.isFalse(containsDuplicates(list_without_dups))
})
it(`use string-IDs for input parameters`, function () {
const list_without_dups: string[] = [
'EA85602C1B41F6F1F5E83C0E6B87142FB8957BD209469E4CC347BA2D0C26F66A',
'F9F89FBB1426210D58D6A06E5EEF1783D6A90EE403B79AEDF0FED36A6DE238D2',
'5328F2D1D6EBBC6093DC10F1EA3DD630666F5B2491EB9BDD7DF9A6C45AC12C46',
]
const list_with_duplicates: string[] = [
'EA85602C1B41F6F1F5E83C0E6B87142FB8957BD209469E4CC347BA2D0C26F66A',
'EA85602C1B41F6F1F5E83C0E6B87142FB8957BD209469E4CC347BA2D0C26F66A',
]
assert.isFalse(containsDuplicates(list_without_dups))
assert.isTrue(containsDuplicates(list_with_duplicates))
})
})
describe('isFlagEnabled', function () {
let flags: number
const flag1 = 0x00010000