(core) Add Support Grist page and nudge

Summary:
Adds a new Support Grist page (accessible only in grist-core), containing
options to opt in to telemetry and sponsor Grist Labs on GitHub.

A nudge is also shown in the doc menu, which can be collapsed or permanently
dismissed.

Test Plan: Browser and server tests.

Reviewers: paulfitz, dsagal

Reviewed By: paulfitz

Subscribers: jarek, dsagal

Differential Revision: https://phab.getgrist.com/D3926
This commit is contained in:
George Gevoian
2023-07-04 17:21:34 -04:00
parent 051c6d52fe
commit 35237a5835
47 changed files with 1743 additions and 365 deletions

View File

@@ -1,4 +1,4 @@
import {buildTelemetryEventChecker, filterMetadata, TelemetryEvent} from 'app/common/Telemetry';
import {buildTelemetryEventChecker, TelemetryEvent} from 'app/common/Telemetry';
import {assert} from 'chai';
describe('Telemetry', function() {
@@ -132,84 +132,4 @@ describe('Telemetry', function() {
);
});
});
describe('filterMetadata', function() {
it('returns filtered and flattened metadata when maxLevel is "full"', function() {
const metadata = {
limited: {
foo: 'abc',
},
full: {
bar: '123',
},
};
assert.deepEqual(filterMetadata(metadata, 'full'), {
foo: 'abc',
bar: '123',
});
});
it('returns filtered and flattened metadata when maxLevel is "limited"', function() {
const metadata = {
limited: {
foo: 'abc',
},
full: {
bar: '123',
},
};
assert.deepEqual(filterMetadata(metadata, 'limited'), {
foo: 'abc',
});
});
it('returns undefined when maxLevel is "off"', function() {
assert.isUndefined(filterMetadata(undefined, 'off'));
});
it('returns an empty object when metadata is empty', function() {
assert.isEmpty(filterMetadata({}, 'full'));
});
it('returns undefined when metadata is undefined', function() {
assert.isUndefined(filterMetadata(undefined, 'full'));
});
it('does not mutate metadata', function() {
const metadata = {
limited: {
foo: 'abc',
},
full: {
bar: '123',
},
};
filterMetadata(metadata, 'limited');
assert.deepEqual(metadata, {
limited: {
foo: 'abc',
},
full: {
bar: '123',
},
});
});
it('excludes keys with nullish values', function() {
const metadata = {
limited: {
foo1: null,
foo2: 'abc',
},
full: {
bar1: undefined,
bar2: '123',
},
};
assert.deepEqual(filterMetadata(metadata, 'full'), {
foo2: 'abc',
bar2: '123',
});
});
});
});

View File

@@ -0,0 +1,308 @@
import {GristLoadConfig} from 'app/common/gristUrls';
import {TelemetryLevel} from 'app/common/Telemetry';
import {assert, driver} from 'mocha-webdriver';
import * as gu from 'test/nbrowser/gristUtils';
import {server, setupTestSuite} from 'test/nbrowser/testUtils';
import * as testUtils from 'test/server/testUtils';
describe('SupportGrist', function() {
this.timeout(30000);
setupTestSuite();
let oldEnv: testUtils.EnvironmentSnapshot;
let session: gu.Session;
afterEach(() => gu.checkForErrors());
describe('in grist-core', function() {
before(async function() {
oldEnv = new testUtils.EnvironmentSnapshot();
process.env.GRIST_TEST_SERVER_DEPLOYMENT_TYPE = 'core';
process.env.GRIST_DEFAULT_EMAIL = gu.session().email;
await server.restart();
});
after(async function() {
oldEnv.restore();
await server.restart();
});
describe('when user is not a manager', function() {
before(async function() {
oldEnv = new testUtils.EnvironmentSnapshot();
await server.restart();
session = await gu.session().user('user2').personalSite.login();
await session.loadDocMenu('/');
});
after(async function() {
oldEnv.restore();
});
it('does not show a nudge on the doc menu', async function() {
await assertNudgeButtonShown(false);
await assertNudgeCardShown(false);
});
it('shows a link to the Support Grist page in the user menu', async function() {
await gu.openAccountMenu();
await driver.find('.test-usermenu-support-grist').click();
assert.isTrue(await driver.findContentWait(
'.test-support-grist-page-sponsorship-section',
/Sponsor Grist Labs on GitHub/,
4000
).isDisplayed());
});
it('shows a message that telemetry is managed by the site administrator', async function() {
assert.isTrue(await driver.findContentWait(
'.test-support-grist-page-telemetry-section',
/This instance is opted out of telemetry\. Only the site administrator has permission to change this\./,
4000
).isDisplayed());
process.env.GRIST_TELEMETRY_LEVEL = 'limited';
await server.restart();
await driver.navigate().refresh();
assert.isTrue(await driver.findContentWait(
'.test-support-grist-page-telemetry-section',
/This instance is opted in to telemetry\. Only the site administrator has permission to change this\./,
4000
).isDisplayed());
});
});
describe('when user is a manager', function() {
before(async function() {
oldEnv = new testUtils.EnvironmentSnapshot();
await server.restart();
session = await gu.session().personalSite.login();
await session.loadDocMenu('/');
});
after(async function() {
oldEnv.restore();
});
it('shows a nudge on the doc menu', async function() {
// Check that the nudge is expanded by default.
await assertNudgeButtonShown(false);
await assertNudgeCardShown(true);
// Reload the doc menu and check that it's still expanded.
await session.loadDocMenu('/');
await assertNudgeButtonShown(false);
await assertNudgeCardShown(true);
// Close the nudge and check that it's now collapsed.
await driver.find('.test-support-grist-nudge-card-close').click();
await assertNudgeButtonShown(true);
await assertNudgeCardShown(false);
// Reload again, and check that it's still collapsed.
await session.loadDocMenu('/');
await assertNudgeButtonShown(true);
await assertNudgeCardShown(false);
// Dismiss the contribute button and check that it's now gone, even after reloading.
await driver.find('.test-support-grist-nudge-contribute-button').mouseMove();
await driver.find('.test-support-grist-nudge-contribute-button-close').click();
await assertNudgeButtonShown(false);
await assertNudgeCardShown(false);
await session.loadDocMenu('/');
await assertNudgeButtonShown(false);
await assertNudgeCardShown(false);
});
it('shows a link to the Support Grist page in the user menu', async function() {
await gu.openAccountMenu();
await driver.find('.test-usermenu-support-grist').click();
await driver.findContentWait('.test-support-grist-page-telemetry-section button', /Opt in to Telemetry/, 2000);
assert.isFalse(await driver.find('.test-support-grist-page-telemetry-section-message').isPresent());
});
it('supports opting in to telemetry from the page', async function() {
await assertTelemetryLevel('off');
await driver.findContentWait(
'.test-support-grist-page-telemetry-section button', /Opt in to Telemetry/, 2000).click();
await driver.findContentWait('.test-support-grist-page-telemetry-section button', /Opt out of Telemetry/, 2000);
assert.equal(
await driver.find('.test-support-grist-page-telemetry-section-message').getText(),
'You have opted in to telemetry. Thank you! 🙏'
);
// Reload the page and check that the Grist config indicates telemetry is set to "limited".
await driver.navigate().refresh();
await driver.findContentWait('.test-support-grist-page-telemetry-section button', /Opt out of Telemetry/, 2000);
assert.equal(
await driver.findWait('.test-support-grist-page-telemetry-section-message', 2000).getText(),
'You have opted in to telemetry. Thank you! 🙏'
);
await assertTelemetryLevel('limited');
});
it('supports opting out of telemetry from the page', async function() {
await driver.findContent('.test-support-grist-page-telemetry-section button', /Opt out of Telemetry/).click();
await driver.findContentWait('.test-support-grist-page-telemetry-section button', /Opt in to Telemetry/, 2000);
assert.isFalse(await driver.find('.test-support-grist-page-telemetry-section-message').isPresent());
// Reload the page and check that the Grist config indicates telemetry is set to "off".
await driver.navigate().refresh();
await driver.findContentWait('.test-support-grist-page-telemetry-section button', /Opt in to Telemetry/, 2000);
assert.isFalse(await driver.find('.test-support-grist-page-telemetry-section-message').isPresent());
await assertTelemetryLevel('off');
});
it('supports opting in to telemetry from the nudge', async function() {
// Reset all dismissed popups, including the telemetry nudge.
await driver.executeScript('resetDismissedPopups();');
await gu.waitForServer();
await session.loadDocMenu('/');
// Opt in to telemetry and reload the page.
await driver.find('.test-support-grist-nudge-card-opt-in').click();
await driver.findWait('.test-support-grist-nudge-card-close-button', 1000).click();
await assertNudgeButtonShown(false);
await assertNudgeCardShown(false);
await session.loadDocMenu('/');
// Check that the nudge is no longer shown and telemetry is set to "limited".
await assertNudgeButtonShown(false);
await assertNudgeCardShown(false);
await assertTelemetryLevel('limited');
});
it('does not show the nudge if telemetry is enabled', async function() {
// Reset all dismissed popups, including the telemetry nudge.
await driver.executeScript('resetDismissedPopups();');
await gu.waitForServer();
// Reload the doc menu and check that the nudge still isn't shown.
await session.loadDocMenu('/');
await assertNudgeButtonShown(false);
await assertNudgeCardShown(false);
// Disable telemetry from the Support Grist page.
await gu.openAccountMenu();
await driver.find('.test-usermenu-support-grist').click();
await driver.findContentWait(
'.test-support-grist-page-telemetry-section button', /Opt out of Telemetry/, 2000).click();
await driver.findContentWait('.test-support-grist-page-telemetry-section button', /Opt in to Telemetry/, 2000);
// Reload the doc menu and check that the nudge is now shown.
await gu.loadDocMenu('/');
await assertNudgeButtonShown(false);
await assertNudgeCardShown(true);
});
it('shows telemetry opt-in status even when set via environment variable', async function() {
// Set the telemetry level to "limited" via environment variable and restart the server.
process.env.GRIST_TELEMETRY_LEVEL = 'limited';
await server.restart();
// Check that the Support Grist page reports telemetry is enabled.
await gu.loadDocMenu('/');
await gu.openAccountMenu();
await driver.find('.test-usermenu-support-grist').click();
assert.equal(
await driver.findWait('.test-support-grist-page-telemetry-section-message', 2000).getText(),
'You have opted in to telemetry. Thank you! 🙏'
);
assert.isFalse(await driver.findContent('.test-support-grist-page-telemetry-section button',
/Opt out of Telemetry/).isPresent());
// Now set the telemetry level to "off" and restart the server.
process.env.GRIST_TELEMETRY_LEVEL = 'off';
await server.restart();
// Check that the Support Grist page reports telemetry is disabled.
await gu.loadDocMenu('/');
await gu.openAccountMenu();
await driver.find('.test-usermenu-support-grist').click();
assert.equal(
await driver.findWait('.test-support-grist-page-telemetry-section-message', 2000).getText(),
'You have opted out of telemetry.'
);
assert.isFalse(await driver.findContent('.test-support-grist-page-telemetry-section button',
/Opt in to Telemetry/).isPresent());
});
});
});
describe('in grist-saas', function() {
before(async function() {
oldEnv = new testUtils.EnvironmentSnapshot();
process.env.GRIST_TEST_SERVER_DEPLOYMENT_TYPE = 'saas';
process.env.GRIST_DEFAULT_EMAIL = gu.session().email;
await server.restart();
session = await gu.session().personalSite.login();
await session.loadDocMenu('/');
});
after(async function() {
oldEnv.restore();
await server.restart();
});
it('does not show a nudge on the doc menu', async function() {
await assertNudgeButtonShown(false);
await assertNudgeCardShown(false);
});
it('does not show a link to the Support Grist page in the user menu', async function() {
await gu.openAccountMenu();
assert.isFalse(await driver.find('.test-usermenu-support-grist').isPresent());
});
});
describe('in grist-enterprise', function() {
before(async function() {
oldEnv = new testUtils.EnvironmentSnapshot();
process.env.GRIST_TEST_SERVER_DEPLOYMENT_TYPE = 'enterprise';
process.env.GRIST_DEFAULT_EMAIL = gu.session().email;
await server.restart();
session = await gu.session().personalSite.login();
await session.loadDocMenu('/');
});
after(async function() {
oldEnv.restore();
await server.restart();
});
it('does not show a nudge on the doc menu', async function() {
await assertNudgeButtonShown(false);
await assertNudgeCardShown(false);
});
it('does not show a link to the Support Grist page in the user menu', async function() {
await gu.openAccountMenu();
assert.isFalse(await driver.find('.test-usermenu-support-grist').isPresent());
});
});
});
async function assertNudgeButtonShown(isShown: boolean) {
if (isShown) {
assert.isTrue(
await driver.find('.test-support-grist-nudge-contribute-button').isDisplayed()
);
} else {
assert.isFalse(await driver.find('.test-support-grist-nudge-contribute-button').isPresent());
}
}
async function assertNudgeCardShown(isShown: boolean) {
if (isShown) {
assert.isTrue(
await driver.find('.test-support-grist-nudge-card').isDisplayed()
);
} else {
assert.isFalse(await driver.find('.test-support-grist-nudge-card').isPresent());
}
}
async function assertTelemetryLevel(level: TelemetryLevel) {
const {telemetry}: GristLoadConfig = await driver.executeScript('return window.gristConfig');
assert.equal(telemetry?.telemetryLevel, level);
}

View File

@@ -34,7 +34,7 @@ async function activateServer(home: FlexServer, docManager: DocManager) {
home.addJsonSupport();
await home.addLandingPages();
home.addHomeApi();
home.addTelemetry();
await home.addTelemetry();
await home.addDoc();
home.addApiErrorHandlers();
serverUrl = home.getOwnUrl();

View File

@@ -1,7 +1,8 @@
import {GristDeploymentType} from 'app/common/gristUrls';
import {PrefSource} from 'app/common/InstallAPI';
import {TelemetryEvent, TelemetryLevel} from 'app/common/Telemetry';
import {ILogMeta, LogMethods} from 'app/server/lib/LogMethods';
import {ITelemetry, Telemetry} from 'app/server/lib/Telemetry';
import {filterMetadata, ITelemetry, Telemetry} from 'app/server/lib/Telemetry';
import axios from 'axios';
import {assert} from 'chai';
import * as sinon from 'sinon';
@@ -9,36 +10,50 @@ import {TestServer} from 'test/gen-server/apiUtils';
import {configForUser} from 'test/gen-server/testUtils';
const chimpy = configForUser('Chimpy');
const kiwi = configForUser('Kiwi');
const anon = configForUser('Anonymous');
describe('Telemetry', function() {
const deploymentTypesAndTelemetryLevels: [GristDeploymentType, TelemetryLevel][] = [
['saas', 'off'],
['saas', 'limited'],
['saas', 'full'],
['core', 'off'],
['core', 'limited'],
['core', 'full'],
const variants: [GristDeploymentType, TelemetryLevel, PrefSource][] = [
['saas', 'off', 'environment-variable'],
['saas', 'limited', 'environment-variable'],
['saas', 'full', 'environment-variable'],
['core', 'off', 'environment-variable'],
['core', 'limited', 'environment-variable'],
['core', 'full', 'environment-variable'],
['core', 'off', 'preferences'],
['core', 'limited', 'preferences'],
['core', 'full', 'preferences'],
];
for (const [deploymentType, telemetryLevel] of deploymentTypesAndTelemetryLevels) {
describe(`in grist-${deploymentType} with a telemetry level of "${telemetryLevel}"`, function() {
for (const [deploymentType, telemetryLevel, settingSource] of variants) {
describe(`in grist-${deploymentType} with level "${telemetryLevel}" set via ${settingSource}`, function() {
let server: TestServer;
let homeUrl: string;
let installationId: string;
let server: TestServer;
let telemetry: ITelemetry;
let forwardEventSpy: sinon.SinonSpy;
let postJsonPayloadStub: sinon.SinonStub;
let doForwardEventStub: sinon.SinonStub;
const sandbox = sinon.createSandbox();
const loggedEvents: [TelemetryEvent, ILogMeta][] = [];
before(async function() {
process.env.GRIST_TEST_SERVER_DEPLOYMENT_TYPE = deploymentType;
process.env.GRIST_TELEMETRY_LEVEL = telemetryLevel;
if (settingSource === 'environment-variable') {
process.env.GRIST_TELEMETRY_LEVEL = telemetryLevel;
}
process.env.GRIST_DEFAULT_EMAIL = 'chimpy@getgrist.com';
server = new TestServer(this);
homeUrl = await server.start();
if (settingSource ==='preferences') {
await axios.patch(`${homeUrl}/api/install/prefs`, {
telemetry: {telemetryLevel},
}, chimpy);
}
installationId = (await server.server.getActivations().current()).id;
telemetry = server.server.getTelemetry();
sandbox
.stub(LogMethods.prototype, 'rawLog')
.callsFake((_level: string, _info: unknown, name: string, meta: ILogMeta) => {
@@ -46,22 +61,39 @@ describe('Telemetry', function() {
});
forwardEventSpy = sandbox
.spy(Telemetry.prototype as any, '_forwardEvent');
postJsonPayloadStub = sandbox
.stub(Telemetry.prototype as any, '_postJsonPayload');
telemetry = server.server.getTelemetry();
doForwardEventStub = sandbox
.stub(Telemetry.prototype as any, '_doForwardEvent');
});
after(async function() {
await server.stop();
sandbox.restore();
delete process.env.GRIST_TEST_SERVER_DEPLOYMENT_TYPE;
delete process.env.GRIST_TELEMETRY_LEVEL;
delete process.env.GRIST_DEFAULT_EMAIL;
await server.stop();
sandbox.restore();
});
it('returns the current telemetry level', async function() {
assert.equal(telemetry.getTelemetryLevel(), telemetryLevel);
it('returns the current telemetry config', async function() {
assert.deepEqual(telemetry.getTelemetryConfig(), {
telemetryLevel,
});
});
if (deploymentType === 'core') {
it('returns the current telemetry status', async function() {
const resp = await axios.get(`${homeUrl}/api/install/prefs`, chimpy);
assert.equal(resp.status, 200);
assert.deepEqual(resp.data, {
telemetry: {
telemetryLevel: {
value: telemetryLevel,
source: settingSource,
},
},
});
});
}
if (telemetryLevel !== 'off') {
if (deploymentType === 'saas') {
it('logs telemetry events', async function() {
@@ -77,7 +109,7 @@ describe('Telemetry', function() {
{
eventName: 'documentOpened',
eventSource: `grist-${deploymentType}`,
docIdDigest: 'digest',
docIdDigest: 'dige:Vq9L3nCkeufQ8euzDkXtM2Fl1cnsALqakjEeM6QlbXQ=',
isPublic: false,
}
]);
@@ -98,7 +130,7 @@ describe('Telemetry', function() {
{
eventName: 'documentOpened',
eventSource: `grist-${deploymentType}`,
docIdDigest: 'digest',
docIdDigest: 'dige:Vq9L3nCkeufQ8euzDkXtM2Fl1cnsALqakjEeM6QlbXQ=',
isPublic: false,
userId: 1,
}
@@ -120,10 +152,11 @@ describe('Telemetry', function() {
assert.deepEqual(forwardEventSpy.lastCall.args, [
'documentOpened',
{
docIdDigest: 'digest',
docIdDigest: 'dige:Vq9L3nCkeufQ8euzDkXtM2Fl1cnsALqakjEeM6QlbXQ=',
isPublic: false,
}
]);
assert.equal(forwardEventSpy.callCount, 1);
}
if (telemetryLevel === 'full') {
@@ -139,14 +172,15 @@ describe('Telemetry', function() {
assert.deepEqual(forwardEventSpy.lastCall.args, [
'documentOpened',
{
docIdDigest: 'digest',
docIdDigest: 'dige:Vq9L3nCkeufQ8euzDkXtM2Fl1cnsALqakjEeM6QlbXQ=',
isPublic: false,
userId: 1,
}
]);
// An earlier test triggered an apiUsage event.
assert.equal(forwardEventSpy.callCount, 2);
}
assert.equal(forwardEventSpy.callCount, 1);
assert.isEmpty(loggedEvents);
});
}
@@ -179,13 +213,6 @@ describe('Telemetry', function() {
});
if (telemetryLevel === 'limited') {
it('throws an error when an event requires an elevated telemetry level', async function() {
await assert.isRejected(
telemetry.logEvent('signupVerified', {}),
/Telemetry event signupVerified requires a minimum telemetry level of 2 but the current level is 1/
);
});
it("throws an error when an event's metadata requires an elevated telemetry level", async function() {
await assert.isRejected(
telemetry.logEvent('documentOpened', {limited: {userId: 1}}),
@@ -290,16 +317,16 @@ describe('Telemetry', function() {
if (telemetryLevel === 'limited') {
assert.equal(forwardEventSpy.callCount, 2);
} else {
// The POST above also triggers an "apiUsage" event.
assert.equal(forwardEventSpy.callCount, 3);
assert.equal(forwardEventSpy.secondCall.args[0], 'apiUsage');
// The count below includes 2 apiUsage events triggered as side effects.
assert.equal(forwardEventSpy.callCount, 4);
assert.equal(forwardEventSpy.thirdCall.args[0], 'apiUsage');
}
assert.isEmpty(loggedEvents);
});
it('skips forwarding events if too many requests are pending', async function() {
let numRequestsMade = 0;
postJsonPayloadStub.callsFake(async () => {
doForwardEventStub.callsFake(async () => {
numRequestsMade += 1;
await new Promise(resolve => setTimeout(resolve, 1000));
});
@@ -329,4 +356,169 @@ describe('Telemetry', function() {
}
});
}
describe('api', function() {
let server: TestServer;
let homeUrl: string;
const sandbox = sinon.createSandbox();
before(async function() {
process.env.GRIST_TEST_SERVER_DEPLOYMENT_TYPE = 'core';
process.env.GRIST_DEFAULT_EMAIL = 'chimpy@getgrist.com';
server = new TestServer(this);
homeUrl = await server.start();
sandbox.stub(Telemetry.prototype as any, '_doForwardEvent');
});
after(async function() {
delete process.env.GRIST_TEST_SERVER_DEPLOYMENT_TYPE;
delete process.env.GRIST_DEFAULT_EMAIL;
await server.stop();
sandbox.restore();
});
it('GET /install/prefs returns 200 for non-default users', async function() {
const resp = await axios.get(`${homeUrl}/api/install/prefs`, kiwi);
assert.equal(resp.status, 200);
assert.deepEqual(resp.data, {
telemetry: {
telemetryLevel: {
value: 'off',
source: 'preferences',
},
},
});
});
it('GET /install/prefs returns 200 for the default user', async function() {
const resp = await axios.get(`${homeUrl}/api/install/prefs`, chimpy);
assert.equal(resp.status, 200);
assert.deepEqual(resp.data, {
telemetry: {
telemetryLevel: {
value: 'off',
source: 'preferences',
},
},
});
});
it('PATCH /install/prefs returns 403 for non-default users', async function() {
const resp = await axios.patch(`${homeUrl}/api/install/prefs`, {
telemetry: {telemetryLevel: 'limited'},
}, kiwi);
assert.equal(resp.status, 403);
});
it('PATCH /install/prefs returns 200 for the default user', async function() {
let resp = await axios.patch(`${homeUrl}/api/install/prefs`, {
telemetry: {telemetryLevel: 'limited'},
}, chimpy);
assert.equal(resp.status, 200);
resp = await axios.get(`${homeUrl}/api/install/prefs`, chimpy);
assert.deepEqual(resp.data, {
telemetry: {
telemetryLevel: {
value: 'limited',
source: 'preferences',
},
},
});
});
});
describe('filterMetadata', function() {
it('returns filtered and flattened metadata when maxLevel is "full"', function() {
const metadata = {
limited: {
foo: 'abc',
},
full: {
bar: '123',
},
};
assert.deepEqual(filterMetadata(metadata, 'full'), {
foo: 'abc',
bar: '123',
});
});
it('returns filtered and flattened metadata when maxLevel is "limited"', function() {
const metadata = {
limited: {
foo: 'abc',
},
full: {
bar: '123',
},
};
assert.deepEqual(filterMetadata(metadata, 'limited'), {
foo: 'abc',
});
});
it('returns undefined when maxLevel is "off"', function() {
assert.isUndefined(filterMetadata(undefined, 'off'));
});
it('returns an empty object when metadata is empty', function() {
assert.isEmpty(filterMetadata({}, 'full'));
});
it('returns undefined when metadata is undefined', function() {
assert.isUndefined(filterMetadata(undefined, 'full'));
});
it('does not mutate metadata', function() {
const metadata = {
limited: {
foo: 'abc',
},
full: {
bar: '123',
},
};
filterMetadata(metadata, 'limited');
assert.deepEqual(metadata, {
limited: {
foo: 'abc',
},
full: {
bar: '123',
},
});
});
it('excludes keys with nullish values', function() {
const metadata = {
limited: {
foo1: null,
foo2: 'abc',
},
full: {
bar1: undefined,
bar2: '123',
},
};
assert.deepEqual(filterMetadata(metadata, 'full'), {
foo2: 'abc',
bar2: '123',
});
});
it('hashes keys suffixed with "Digest"', function() {
const metadata = {
limited: {
docIdDigest: 'FGWGX4S6TB6',
docId: '3WH3D68J28',
},
};
assert.deepEqual(filterMetadata(metadata, 'limited'), {
docIdDigest: 'FGWG:omhYAysWiM7coZK+FLK/tIOPW4BaowXjU7J/P9ynYcU=',
docId: '3WH3D68J28',
});
});
});
});