vault/ui/tests/integration/utils/client-count-utils-test.js
hashicorp-copywrite[bot] 0b12cdcfd1
[COMPLIANCE] License changes (#22290)
* Adding explicit MPL license for sub-package.

This directory and its subdirectories (packages) contain files licensed with the MPLv2 `LICENSE` file in this directory and are intentionally licensed separately from the BSL `LICENSE` file at the root of this repository.

* Adding explicit MPL license for sub-package.

This directory and its subdirectories (packages) contain files licensed with the MPLv2 `LICENSE` file in this directory and are intentionally licensed separately from the BSL `LICENSE` file at the root of this repository.

* Updating the license from MPL to Business Source License.

Going forward, this project will be licensed under the Business Source License v1.1. Please see our blog post for more details at https://hashi.co/bsl-blog, FAQ at www.hashicorp.com/licensing-faq, and details of the license at www.hashicorp.com/bsl.

* add missing license headers

* Update copyright file headers to BUS-1.1

* Fix test that expected exact offset on hcl file

---------

Co-authored-by: hashicorp-copywrite[bot] <110428419+hashicorp-copywrite[bot]@users.noreply.github.com>
Co-authored-by: Sarah Thompson <sthompson@hashicorp.com>
Co-authored-by: Brian Kassouf <bkassouf@hashicorp.com>
2023-08-10 18:14:03 -07:00

931 lines
28 KiB
JavaScript

/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import { module, test } from 'qunit';
import { setupTest } from 'ember-qunit';
import {
flattenDataset,
formatByMonths,
formatByNamespace,
homogenizeClientNaming,
sortMonthsByTimestamp,
namespaceArrayToObject,
} from 'core/utils/client-count-utils';
import { parseAPITimestamp } from 'core/utils/date-formatters';
import isBefore from 'date-fns/isBefore';
import isAfter from 'date-fns/isAfter';
// import { setupMirage } from 'ember-cli-mirage/test-support';
// import ENV from 'vault/config/environment';
// import { formatRFC3339 } from 'date-fns';
module('Integration | Util | client count utils', function (hooks) {
setupTest(hooks);
// setupMirage(hooks);
// TODO: wire up to stubbed API/mirage?
// hooks.before(function () {
// ENV['ember-cli-mirage'].handler = 'clients';
// });
// hooks.after(function () {
// ENV['ember-cli-mirage'].handler = null;
// });
/* MONTHS array contains: (update when backend work done on months )
- one month with only old client naming
*/
const MONTHS = [
{
timestamp: '2021-05-01T00:00:00Z',
counts: {
distinct_entities: 25,
non_entity_tokens: 25,
clients: 50,
},
namespaces: [
{
namespace_id: 'root',
namespace_path: '',
counts: {
distinct_entities: 13,
non_entity_tokens: 7,
clients: 20,
},
mounts: [
{
mount_path: 'auth/up2/',
counts: {
distinct_entities: 8,
non_entity_tokens: 0,
clients: 8,
},
},
{
mount_path: 'auth/up1/',
counts: {
distinct_entities: 0,
non_entity_tokens: 7,
clients: 7,
},
},
],
},
{
namespace_id: 's07UR',
namespace_path: 'ns1/',
counts: {
distinct_entities: 5,
non_entity_tokens: 5,
clients: 10,
},
mounts: [
{
mount_path: 'auth/up1/',
counts: {
distinct_entities: 0,
non_entity_tokens: 5,
clients: 5,
},
},
{
mount_path: 'auth/up2/',
counts: {
distinct_entities: 5,
non_entity_tokens: 0,
clients: 5,
},
},
],
},
],
new_clients: {
counts: {
distinct_entities: 3,
non_entity_tokens: 2,
clients: 5,
},
namespaces: [
{
namespace_id: 'root',
namespace_path: '',
counts: {
distinct_entities: 3,
non_entity_tokens: 2,
clients: 5,
},
mounts: [
{
mount_path: 'auth/up2/',
counts: {
distinct_entities: 3,
non_entity_tokens: 0,
clients: 3,
},
},
{
mount_path: 'auth/up1/',
counts: {
distinct_entities: 0,
non_entity_tokens: 2,
clients: 2,
},
},
],
},
],
},
},
{
timestamp: '2021-10-01T00:00:00Z',
counts: {
distinct_entities: 20,
entity_clients: 20,
non_entity_tokens: 20,
non_entity_clients: 20,
clients: 40,
},
namespaces: [
{
namespace_id: 'root',
namespace_path: '',
counts: {
distinct_entities: 8,
entity_clients: 8,
non_entity_tokens: 7,
non_entity_clients: 7,
clients: 15,
},
mounts: [
{
mount_path: 'auth/up2/',
counts: {
distinct_entities: 8,
entity_clients: 8,
non_entity_tokens: 0,
non_entity_clients: 0,
clients: 8,
},
},
{
mount_path: 'auth/up1/',
counts: {
distinct_entities: 0,
entity_clients: 0,
non_entity_tokens: 7,
non_entity_clients: 7,
clients: 7,
},
},
],
},
{
namespace_id: 's07UR',
namespace_path: 'ns1/',
counts: {
distinct_entities: 5,
entity_clients: 5,
non_entity_tokens: 5,
non_entity_clients: 5,
clients: 10,
},
mounts: [
{
mount_path: 'auth/up1/',
counts: {
distinct_entities: 0,
entity_clients: 0,
non_entity_tokens: 5,
non_entity_clients: 5,
clients: 5,
},
},
{
mount_path: 'auth/up2/',
counts: {
distinct_entities: 5,
entity_clients: 5,
non_entity_tokens: 0,
non_entity_clients: 0,
clients: 5,
},
},
],
},
],
new_clients: {
counts: {
distinct_entities: 3,
entity_clients: 3,
non_entity_tokens: 2,
non_entity_clients: 2,
clients: 5,
},
namespaces: [
{
namespace_id: 'root',
namespace_path: '',
counts: {
distinct_entities: 3,
entity_clients: 3,
non_entity_tokens: 2,
non_entity_clients: 2,
clients: 5,
},
mounts: [
{
mount_path: 'auth/up2/',
counts: {
distinct_entities: 3,
entity_clients: 3,
non_entity_tokens: 0,
non_entity_clients: 0,
clients: 3,
},
},
{
mount_path: 'auth/up1/',
counts: {
distinct_entities: 0,
entity_clients: 0,
non_entity_tokens: 2,
non_entity_clients: 2,
clients: 2,
},
},
],
},
],
},
},
{
timestamp: '2021-09-01T00:00:00Z',
counts: {
distinct_entities: 0,
entity_clients: 17,
non_entity_tokens: 0,
non_entity_clients: 18,
clients: 35,
},
namespaces: [
{
namespace_id: 'oImjk',
namespace_path: 'ns2/',
counts: {
distinct_entities: 0,
entity_clients: 5,
non_entity_tokens: 0,
non_entity_clients: 5,
clients: 10,
},
mounts: [
{
mount_path: 'auth/up1/',
counts: {
distinct_entities: 0,
entity_clients: 0,
non_entity_tokens: 0,
non_entity_clients: 5,
clients: 5,
},
},
{
mount_path: 'auth/up2/',
counts: {
distinct_entities: 0,
entity_clients: 5,
non_entity_tokens: 0,
non_entity_clients: 0,
clients: 5,
},
},
],
},
{
namespace_id: 'root',
namespace_path: '',
counts: {
distinct_entities: 0,
entity_clients: 2,
non_entity_tokens: 0,
non_entity_clients: 3,
clients: 5,
},
mounts: [
{
mount_path: 'auth/up1/',
counts: {
distinct_entities: 0,
entity_clients: 0,
non_entity_tokens: 0,
non_entity_clients: 3,
clients: 3,
},
},
{
mount_path: 'auth/up2/',
counts: {
distinct_entities: 0,
entity_clients: 2,
non_entity_tokens: 0,
non_entity_clients: 0,
clients: 2,
},
},
],
},
{
namespace_id: 's07UR',
namespace_path: 'ns1/',
counts: {
distinct_entities: 0,
entity_clients: 3,
non_entity_tokens: 0,
non_entity_clients: 2,
clients: 5,
},
mounts: [
{
mount_path: 'auth/up2/',
counts: {
distinct_entities: 0,
entity_clients: 3,
non_entity_tokens: 0,
non_entity_clients: 0,
clients: 3,
},
},
{
mount_path: 'auth/up1/',
counts: {
distinct_entities: 0,
entity_clients: 0,
non_entity_tokens: 0,
non_entity_clients: 2,
clients: 2,
},
},
],
},
],
new_clients: {
counts: {
distinct_entities: 0,
entity_clients: 10,
non_entity_tokens: 0,
non_entity_clients: 10,
clients: 20,
},
namespaces: [
{
namespace_id: 'oImjk',
namespace_path: 'ns2/',
counts: {
distinct_entities: 0,
entity_clients: 5,
non_entity_tokens: 0,
non_entity_clients: 5,
clients: 10,
},
mounts: [
{
mount_path: 'auth/up1/',
counts: {
distinct_entities: 0,
entity_clients: 0,
non_entity_tokens: 0,
non_entity_clients: 5,
clients: 5,
},
},
{
mount_path: 'auth/up2/',
counts: {
distinct_entities: 0,
entity_clients: 5,
non_entity_tokens: 0,
non_entity_clients: 0,
clients: 5,
},
},
],
},
{
namespace_id: 'root',
namespace_path: '',
counts: {
distinct_entities: 0,
entity_clients: 2,
non_entity_tokens: 0,
non_entity_clients: 3,
clients: 5,
},
mounts: [
{
mount_path: 'auth/up1/',
counts: {
distinct_entities: 0,
entity_clients: 0,
non_entity_tokens: 0,
non_entity_clients: 3,
clients: 3,
},
},
{
mount_path: 'auth/up2/',
counts: {
distinct_entities: 0,
entity_clients: 2,
non_entity_tokens: 0,
non_entity_clients: 0,
clients: 2,
},
},
],
},
{
namespace_id: 's07UR',
namespace_path: 'ns1/',
counts: {
distinct_entities: 0,
entity_clients: 3,
non_entity_tokens: 0,
non_entity_clients: 2,
clients: 5,
},
mounts: [
{
mount_path: 'auth/up2/',
counts: {
distinct_entities: 0,
entity_clients: 3,
non_entity_tokens: 0,
non_entity_clients: 0,
clients: 3,
},
},
{
mount_path: 'auth/up1/',
counts: {
distinct_entities: 0,
entity_clients: 0,
non_entity_tokens: 0,
non_entity_clients: 2,
clients: 2,
},
},
],
},
],
},
},
];
const BY_NAMESPACE = [
{
namespace_id: '96OwG',
namespace_path: 'test-ns/',
counts: {
distinct_entities: 18290,
entity_clients: 18290,
non_entity_tokens: 18738,
non_entity_clients: 18738,
clients: 37028,
},
mounts: [
{
mount_path: 'path-1',
counts: {
distinct_entities: 6403,
entity_clients: 6403,
non_entity_tokens: 6300,
non_entity_clients: 6300,
clients: 12703,
},
},
{
mount_path: 'path-2',
counts: {
distinct_entities: 5699,
entity_clients: 5699,
non_entity_tokens: 6777,
non_entity_clients: 6777,
clients: 12476,
},
},
{
mount_path: 'path-3',
counts: {
distinct_entities: 6188,
entity_clients: 6188,
non_entity_tokens: 5661,
non_entity_clients: 5661,
clients: 11849,
},
},
],
},
{
namespace_id: 'root',
namespace_path: '',
counts: {
distinct_entities: 19099,
entity_clients: 19099,
non_entity_tokens: 17781,
non_entity_clients: 17781,
clients: 36880,
},
mounts: [
{
mount_path: 'path-3',
counts: {
distinct_entities: 6863,
entity_clients: 6863,
non_entity_tokens: 6801,
non_entity_clients: 6801,
clients: 13664,
},
},
{
mount_path: 'path-2',
counts: {
distinct_entities: 6047,
entity_clients: 6047,
non_entity_tokens: 5957,
non_entity_clients: 5957,
clients: 12004,
},
},
{
mount_path: 'path-1',
counts: {
distinct_entities: 6189,
entity_clients: 6189,
non_entity_tokens: 5023,
non_entity_clients: 5023,
clients: 11212,
},
},
{
mount_path: 'auth/up2/',
counts: {
distinct_entities: 0,
entity_clients: 50,
non_entity_tokens: 0,
non_entity_clients: 23,
clients: 73,
},
},
{
mount_path: 'auth/up1/',
counts: {
distinct_entities: 0,
entity_clients: 25,
non_entity_tokens: 0,
non_entity_clients: 15,
clients: 40,
},
},
],
},
];
const EMPTY_MONTHS = [
{
timestamp: '2021-06-01T00:00:00Z',
counts: null,
namespaces: null,
new_clients: null,
},
{
timestamp: '2021-07-01T00:00:00Z',
counts: null,
namespaces: null,
new_clients: null,
},
];
const SOME_OBJECT = { foo: 'bar' };
test('formatByMonths: formats the months array', async function (assert) {
assert.expect(103);
const keyNameAssertions = (object, objectName) => {
const objectKeys = Object.keys(object);
assert.false(objectKeys.includes('counts'), `${objectName} doesn't include 'counts' key`);
assert.true(objectKeys.includes('clients'), `${objectName} includes 'clients' key`);
assert.true(objectKeys.includes('entity_clients'), `${objectName} includes 'entity_clients' key`);
assert.true(
objectKeys.includes('non_entity_clients'),
`${objectName} includes 'non_entity_clients' key`
);
};
const assertClientCounts = (object, originalObject) => {
const newObjectKeys = ['clients', 'entity_clients', 'non_entity_clients'];
const originalKeys = Object.keys(originalObject.counts).includes('entity_clients')
? newObjectKeys
: ['clients', 'distinct_entities', 'non_entity_tokens'];
newObjectKeys.forEach((key, i) => {
assert.strictEqual(
object[key],
originalObject.counts[originalKeys[i]],
`${object.month} ${key} equal original counts`
);
});
};
const formattedMonths = formatByMonths(MONTHS);
assert.notEqual(formattedMonths, MONTHS, 'does not modify original array');
formattedMonths.forEach((month) => {
const originalMonth = MONTHS.find((m) => month.month === parseAPITimestamp(m.timestamp, 'M/yy'));
// if originalMonth is found (not undefined) then the formatted month has an accurate, parsed timestamp
assert.ok(originalMonth, `month has parsed timestamp of ${month.month}`);
assert.ok(month.namespaces_by_key, `month includes 'namespaces_by_key' key`);
keyNameAssertions(month, 'formatted month');
assertClientCounts(month, originalMonth);
assert.ok(month.new_clients.month, 'new clients key has a month key');
keyNameAssertions(month.new_clients, 'formatted month new_clients');
assertClientCounts(month.new_clients, originalMonth.new_clients);
month.namespaces.forEach((namespace) => keyNameAssertions(namespace, 'namespace within month'));
month.new_clients.namespaces.forEach((namespace) =>
keyNameAssertions(namespace, 'new client namespaces within month')
);
});
// method fails gracefully
const expected = [
{
counts: null,
month: '6/21',
namespaces: [],
namespaces_by_key: {},
new_clients: {
month: '6/21',
namespaces: [],
timestamp: '2021-06-01T00:00:00Z',
},
timestamp: '2021-06-01T00:00:00Z',
},
{
counts: null,
month: '7/21',
namespaces: [],
namespaces_by_key: {},
new_clients: {
month: '7/21',
namespaces: [],
timestamp: '2021-07-01T00:00:00Z',
},
timestamp: '2021-07-01T00:00:00Z',
},
];
assert.strictEqual(formatByMonths(SOME_OBJECT), SOME_OBJECT, 'it returns if arg is not an array');
assert.propEqual(expected, formatByMonths(EMPTY_MONTHS), 'it does not error with null months');
assert.ok(formatByMonths([...EMPTY_MONTHS, ...MONTHS]), 'it does not error with combined data');
});
test('formatByNamespace: formats namespace arrays with and without mounts', async function (assert) {
assert.expect(102);
const keyNameAssertions = (object, objectName) => {
const objectKeys = Object.keys(object);
assert.false(objectKeys.includes('counts'), `${objectName} doesn't include 'counts' key`);
assert.true(objectKeys.includes('label'), `${objectName} includes 'label' key`);
assert.true(objectKeys.includes('clients'), `${objectName} includes 'clients' key`);
assert.true(objectKeys.includes('entity_clients'), `${objectName} includes 'entity_clients' key`);
assert.true(
objectKeys.includes('non_entity_clients'),
`${objectName} includes 'non_entity_clients' key`
);
};
const keyValueAssertions = (object, pathName, originalObject) => {
const keysToAssert = ['clients', 'entity_clients', 'non_entity_clients'];
assert.strictEqual(object.label, originalObject[pathName], `${pathName} matches label`);
keysToAssert.forEach((key) => {
assert.strictEqual(object[key], originalObject.counts[key], `number of ${key} equal original`);
});
};
const formattedNamespaces = formatByNamespace(BY_NAMESPACE);
assert.notEqual(formattedNamespaces, MONTHS, 'does not modify original array');
formattedNamespaces.forEach((namespace) => {
const origNamespace = BY_NAMESPACE.find((ns) => ns.namespace_path === namespace.label);
keyNameAssertions(namespace, 'formatted namespace');
keyValueAssertions(namespace, 'namespace_path', origNamespace);
namespace.mounts.forEach((mount) => {
const origMount = origNamespace.mounts.find((m) => m.mount_path === mount.label);
keyNameAssertions(mount, 'formatted mount');
keyValueAssertions(mount, 'mount_path', origMount);
});
});
const nsWithoutMounts = {
namespace_id: '96OwG',
namespace_path: 'no-mounts-ns/',
counts: {
distinct_entities: 18290,
entity_clients: 18290,
non_entity_tokens: 18738,
non_entity_clients: 18738,
clients: 37028,
},
mounts: [],
};
const formattedNsWithoutMounts = formatByNamespace([nsWithoutMounts])[0];
keyNameAssertions(formattedNsWithoutMounts, 'namespace without mounts');
keyValueAssertions(formattedNsWithoutMounts, 'namespace_path', nsWithoutMounts);
assert.strictEqual(formattedNsWithoutMounts.mounts.length, 0, 'formatted namespace has no mounts');
assert.strictEqual(formatByNamespace(SOME_OBJECT), SOME_OBJECT, 'it returns if arg is not an array');
});
test('homogenizeClientNaming: homogenizes key names when both old and new keys exist, or just old key names', async function (assert) {
assert.expect(168);
const keyNameAssertions = (object, objectName) => {
const objectKeys = Object.keys(object);
assert.false(
objectKeys.includes('distinct_entities'),
`${objectName} doesn't include 'distinct_entities' key`
);
assert.false(
objectKeys.includes('non_entity_tokens'),
`${objectName} doesn't include 'non_entity_tokens' key`
);
assert.true(objectKeys.includes('entity_clients'), `${objectName} includes 'entity_clients' key`);
assert.true(
objectKeys.includes('non_entity_clients'),
`${objectName} includes 'non_entity_clients' key`
);
};
const transformedMonths = [...MONTHS];
transformedMonths.forEach((month) => {
month.counts = homogenizeClientNaming(month.counts);
keyNameAssertions(month.counts, 'month counts');
month.new_clients.counts = homogenizeClientNaming(month.new_clients.counts);
keyNameAssertions(month.new_clients.counts, 'month new counts');
month.namespaces.forEach((ns) => {
ns.counts = homogenizeClientNaming(ns.counts);
keyNameAssertions(ns.counts, 'namespace counts');
ns.mounts.forEach((mount) => {
mount.counts = homogenizeClientNaming(mount.counts);
keyNameAssertions(mount.counts, 'mount counts');
});
});
month.new_clients.namespaces.forEach((ns) => {
ns.counts = homogenizeClientNaming(ns.counts);
keyNameAssertions(ns.counts, 'namespace new counts');
ns.mounts.forEach((mount) => {
mount.counts = homogenizeClientNaming(mount.counts);
keyNameAssertions(mount.counts, 'mount new counts');
});
});
});
});
test('flattenDataset: removes the counts key and flattens the dataset', async function (assert) {
assert.expect(22);
const flattenedNamespace = flattenDataset(BY_NAMESPACE[0]);
const flattenedMount = flattenDataset(BY_NAMESPACE[0].mounts[0]);
const flattenedMonth = flattenDataset(MONTHS[0]);
const flattenedNewMonthClients = flattenDataset(MONTHS[0].new_clients);
const objectNullCounts = { counts: null, foo: 'bar' };
const keyNameAssertions = (object, objectName) => {
const objectKeys = Object.keys(object);
assert.false(objectKeys.includes('counts'), `${objectName} doesn't include 'counts' key`);
assert.true(objectKeys.includes('clients'), `${objectName} includes 'clients' key`);
assert.true(objectKeys.includes('entity_clients'), `${objectName} includes 'entity_clients' key`);
assert.true(
objectKeys.includes('non_entity_clients'),
`${objectName} includes 'non_entity_clients' key`
);
};
keyNameAssertions(flattenedNamespace, 'namespace object');
keyNameAssertions(flattenedMount, 'mount object');
keyNameAssertions(flattenedMonth, 'month object');
keyNameAssertions(flattenedNewMonthClients, 'month new_clients object');
assert.strictEqual(
flattenDataset(SOME_OBJECT),
SOME_OBJECT,
"it returns original object if counts key doesn't exist"
);
assert.strictEqual(
flattenDataset(objectNullCounts),
objectNullCounts,
'it returns original object if counts are null'
);
assert.propEqual(
['some array'],
flattenDataset(['some array']),
'it fails gracefully if an array is passed in'
);
assert.strictEqual(flattenDataset(null), null, 'it fails gracefully if null is passed in');
assert.strictEqual(
flattenDataset('some string'),
'some string',
'it fails gracefully if a string is passed in'
);
assert.propEqual(
new Object(),
flattenDataset(new Object()),
'it fails gracefully if an empty object is passed in'
);
});
test('sortMonthsByTimestamp: sorts timestamps chronologically, oldest to most recent', async function (assert) {
assert.expect(4);
const sortedMonths = sortMonthsByTimestamp(MONTHS);
assert.ok(
isBefore(parseAPITimestamp(sortedMonths[0].timestamp), parseAPITimestamp(sortedMonths[1].timestamp)),
'first timestamp date is earlier than second'
);
assert.ok(
isAfter(parseAPITimestamp(sortedMonths[2].timestamp), parseAPITimestamp(sortedMonths[1].timestamp)),
'third timestamp date is later second'
);
assert.notEqual(sortedMonths[1], MONTHS[1], 'it does not modify original array');
assert.strictEqual(sortedMonths[0], MONTHS[0], 'it does not modify original array');
});
test('namespaceArrayToObject: transforms data without modifying original', async function (assert) {
assert.expect(30);
const assertClientCounts = (object, originalObject) => {
const valuesToCheck = ['clients', 'entity_clients', 'non_entity_clients'];
valuesToCheck.forEach((key) => {
assert.strictEqual(object[key], originalObject[key], `${key} equal original counts`);
});
};
const totalClientsByNamespace = formatByNamespace(MONTHS[1].namespaces);
const newClientsByNamespace = formatByNamespace(MONTHS[1].new_clients.namespaces);
const byNamespaceKeyObject = namespaceArrayToObject(
totalClientsByNamespace,
newClientsByNamespace,
'10/21'
);
assert.propEqual(
totalClientsByNamespace,
formatByNamespace(MONTHS[1].namespaces),
'it does not modify original array'
);
assert.propEqual(
newClientsByNamespace,
formatByNamespace(MONTHS[1].new_clients.namespaces),
'it does not modify original array'
);
const namespaceKeys = Object.keys(byNamespaceKeyObject);
namespaceKeys.forEach((nsKey) => {
const newNsObject = byNamespaceKeyObject[nsKey];
const originalNsData = totalClientsByNamespace.find((ns) => ns.label === nsKey);
assertClientCounts(newNsObject, originalNsData);
const mountKeys = Object.keys(newNsObject.mounts_by_key);
mountKeys.forEach((mKey) => {
const mountData = originalNsData.mounts.find((m) => m.label === mKey);
assertClientCounts(newNsObject.mounts_by_key[mKey], mountData);
});
});
namespaceKeys.forEach((nsKey) => {
const newNsObject = byNamespaceKeyObject[nsKey];
const originalNsData = newClientsByNamespace.find((ns) => ns.label === nsKey);
if (!originalNsData) return;
assertClientCounts(newNsObject.new_clients, originalNsData);
const mountKeys = Object.keys(newNsObject.mounts_by_key);
mountKeys.forEach((mKey) => {
const mountData = originalNsData.mounts.find((m) => m.label === mKey);
assertClientCounts(newNsObject.mounts_by_key[mKey].new_clients, mountData);
});
});
assert.propEqual(
{},
namespaceArrayToObject(null, null, '10/21'),
'returns an empty object when totalClientsByNamespace = null'
);
});
});