Skip to content

Commit d8d440d

Browse files
committed
Adds document usage tests
1 parent a348780 commit d8d440d

File tree

1 file changed

+356
-0
lines changed

1 file changed

+356
-0
lines changed

test/nbrowser/DocumentUsage.ts

Lines changed: 356 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,356 @@
1+
import {arrayRepeat} from 'app/common/gutil';
2+
import {UserAPI} from 'app/common/UserAPI';
3+
import {assert, driver, Key} from 'mocha-webdriver';
4+
import fetch from 'node-fetch';
5+
import * as gu from 'test/nbrowser/gristUtils';
6+
import {server} from 'test/nbrowser/testServer';
7+
import {setupTestSuite} from 'test/nbrowser/testUtils';
8+
9+
describe('DocumentUsage', function() {
10+
this.timeout(20000);
11+
const cleanup = setupTestSuite();
12+
13+
const ownerUser = 'user1';
14+
let api: UserAPI;
15+
let session: gu.Session;
16+
17+
before(async function () {
18+
session = await gu.session().user(ownerUser).login();
19+
api = session.createHomeApi();
20+
});
21+
22+
it('shows usage stats on the raw data page', async function() {
23+
await session.tempNewDoc(cleanup, "EmptyUsageDoc");
24+
await testDocUsageStatsAreZero();
25+
});
26+
27+
describe('row usage', function() {
28+
let docId: string;
29+
30+
before(async () => {
31+
docId = await session.tempNewDoc(cleanup, "RowUsageTestDoc");
32+
});
33+
34+
it('updates row usage when rows are added or removed', async function() {
35+
await goToDocUsage();
36+
37+
// Add 1,000 rows, and check that the row count updated.
38+
await api.applyUserActions(docId, [
39+
['AddEmptyTable', "RowCheckTable"],
40+
['BulkAddRecord', 'RowCheckTable', arrayRepeat(1000, null), {}]
41+
]);
42+
await assertRowCount('1,000');
43+
await assertDataSize('0.00');
44+
await assertAttachmentsSize('0.00');
45+
await assertUsageMessage(null);
46+
await gu.assertBannerText(null);
47+
48+
// Add 4,000 rows to a different table, and check that row count updated.
49+
await api.applyUserActions(docId, [
50+
['AddEmptyTable', "RowCheckTable2"],
51+
['BulkAddRecord', 'RowCheckTable2', arrayRepeat(4000, null), {}]
52+
]);
53+
await assertRowCount('5,000');
54+
await assertUsageMessage(null);
55+
await gu.assertBannerText(null);
56+
57+
// Refresh the page, and make sure banners still aren't shown. (Only free team
58+
// sites should currently show them.)
59+
await driver.navigate().refresh();
60+
await waitForDocUsage();
61+
await assertRowCount('5,000');
62+
await assertDataSize('0.00');
63+
await assertAttachmentsSize('0.00');
64+
await assertUsageMessage(null);
65+
await gu.assertBannerText(null);
66+
67+
// Delete the first table, and check that the row count updated.
68+
await api.applyUserActions(docId, [['RemoveTable', 'RowCheckTable']]);
69+
await assertRowCount('4,000');
70+
await assertDataSize('0.00');
71+
await assertAttachmentsSize('0.00');
72+
await assertUsageMessage(null);
73+
await gu.assertBannerText(null);
74+
75+
// Delete the second table so we're back at 0.
76+
await api.applyUserActions(docId, [['RemoveTable', 'RowCheckTable2']]);
77+
await assertRowCount('0');
78+
});
79+
80+
it('updates data size usage over time', async function() {
81+
await goToDocUsage();
82+
83+
// Add 500 rows with some data in them.
84+
await api.applyUserActions(docId, [
85+
['AddEmptyTable', "DataSizeTable"],
86+
['BulkAddRecord', 'DataSizeTable', arrayRepeat(500, null), {
87+
'A': arrayRepeat(500, 'Some random data for testing that data size usage is working as intended.'),
88+
'B': arrayRepeat(500, 2500),
89+
'C': arrayRepeat(500, true),
90+
}]
91+
]);
92+
93+
// Check that size usage is initially unchanged; it's computed on doc startup, and on interval
94+
// in the background, to minimize load.
95+
96+
await assertRowCount('500');
97+
await assertDataSize('0.00');
98+
await assertAttachmentsSize('0.00');
99+
100+
// Force the document to reload, and check that size usage updated.
101+
await api.getDocAPI(docId).forceReload();
102+
await waitForDocUsage();
103+
await assertDataSize('0.04');
104+
await assertAttachmentsSize('0.00');
105+
});
106+
});
107+
108+
describe('attachment usage', function() {
109+
let docId: string;
110+
111+
before(async () => {
112+
docId = await session.tempNewDoc(cleanup, "AttachmentUsageTestDoc");
113+
});
114+
115+
it('updates attachments size usage when uploading attachments', async function () {
116+
// Add a new 'Attachments' column of type Attachment to Table1.
117+
await api.applyUserActions(docId, [['AddEmptyTable', "AttachmentsTable"]]);
118+
await gu.getPageItem('AttachmentsTable').click();
119+
await gu.waitForServer();
120+
await addAttachmentColumn('Attachments');
121+
122+
// Upload some files into the first row. (We're putting Grist docs in a Grist doc!)
123+
await driver.sendKeys(Key.ENTER);
124+
await gu.fileDialogUpload(
125+
'docs/Covid-19.grist,docs/World-v0.grist,docs/World-v1.grist,docs/World-v3.grist,'
126+
+ 'docs/Landlord.grist,docs/ImportReferences.grist,docs/WorldUndo.grist,'
127+
+ 'docs/Ref-List-AC-Test.grist,docs/PasteParsing.grist',
128+
() => driver.find('.test-pw-add').click()
129+
);
130+
// Check all 9 attachments have uploaded.
131+
await driver.findContentWait('.test-pw-counter', /of 9/, 4000);
132+
await driver.find('.test-modal-dialog .test-pw-close').click();
133+
await gu.waitForServer();
134+
135+
// Navigate back to the raw data page, and check that attachments size updated.
136+
await goToDocUsage();
137+
await assertDataSize('0.00');
138+
await assertAttachmentsSize('0.01');
139+
140+
// Delete the 'Attachments' column; usage should not immediately update.
141+
await api.applyUserActions(docId, [['RemoveColumn', 'AttachmentsTable', 'Attachments']]);
142+
await assertDataSize('0.00');
143+
await assertAttachmentsSize('0.01');
144+
145+
// Remove unused attachments via API and check that size automatically updates to 0.
146+
await removeUnusedAttachments(api, docId);
147+
await assertDataSize('0.00');
148+
await assertAttachmentsSize('0.00');
149+
});
150+
});
151+
152+
describe('doc usage access', function() {
153+
let docId: string;
154+
let docUsagePageUrl: string;
155+
156+
before(async () => {
157+
docId = await session.tempNewDoc(cleanup, "DocUsageAccessTestDoc");
158+
159+
// Share the document with everyone as an editor.
160+
await api.updateDocPermissions(docId, {
161+
users: {
162+
'everyone@getgrist.com': 'editors',
163+
[gu.session().user('user2').email]: 'editors',
164+
},
165+
});
166+
167+
await goToDocUsage();
168+
docUsagePageUrl = await driver.getCurrentUrl();
169+
});
170+
171+
it('does not show banners or usage to public visitors', async function() {
172+
// Log in as anon, and check that the delete-only banner is not shown.
173+
await gu.session().anon.login();
174+
await driver.navigate().to(docUsagePageUrl);
175+
await assertUsageAccessDenied();
176+
await gu.assertBannerText(null);
177+
178+
/*
179+
// Delete a few rows, putting the document in "approaching limit" mode. Make sure a banner is
180+
// still not shown.
181+
await api.applyUserActions(docId, [['BulkRemoveRecord', 'Table1', [1, 2, 3]]]);
182+
await assertUsageAccessDenied();
183+
await gu.assertBannerText(null);
184+
185+
// Finally, add back some rows to push the document back into "grace period" mode, and check
186+
// once more that a banner is still not shown.
187+
await api.applyUserActions(docId, [['BulkAddRecord', 'Table1', arrayRepeat(3, null), {}]]);
188+
await assertUsageAccessDenied();
189+
await gu.assertBannerText(null);
190+
*/
191+
});
192+
193+
it('shows usage count to logged in users with edit access', async function() {
194+
await gu.session().user('user2').login();
195+
await driver.navigate().to(docUsagePageUrl);
196+
await goToDocUsage();
197+
await assertRowCount('0');
198+
});
199+
200+
describe('access rules', async function () {
201+
before(async function () {
202+
session = await gu.session().user(ownerUser).login();
203+
await driver.navigate().to(docUsagePageUrl);
204+
// Make Table2 viewable only by the owner.
205+
await blockTable(api, docId, 'Table1');
206+
});
207+
208+
it('show row count for owners if blocked by access rules', async function() {
209+
await goToDocUsage();
210+
await assertUsageAccessAllowed();
211+
});
212+
213+
it('show row count for owners if table is hidden by access rules', async () => {
214+
await hideTable(api, docId, 'Table1');
215+
await assertUsageAccessAllowed();
216+
await assertDataSize('0.00');
217+
await assertAttachmentsSize('0.00');
218+
});
219+
220+
it('does not show row count if blocked by access rules', async () => {
221+
await gu.session().user('user2').login();
222+
await driver.navigate().to(docUsagePageUrl);
223+
await assertUsageAccessDenied();
224+
});
225+
});
226+
227+
describe ('owner', async function () {
228+
229+
});
230+
});
231+
});
232+
233+
async function testDocUsageStatsAreZero() {
234+
// Check that the Usage section exists.
235+
await goToDocUsage();
236+
assert.equal(await driver.find('.test-doc-usage-heading').getText(), 'Usage');
237+
await assertUsageMessage(null);
238+
239+
// Check that usage is at 0.
240+
await assertRowCount('0');
241+
await assertDataSize('0.00');
242+
await assertAttachmentsSize('0.00');
243+
244+
// Check that banners aren't shown on the raw data page.
245+
await gu.assertBannerText(null);
246+
}
247+
248+
async function getTableResourceAclId(
249+
api: UserAPI, docId: string, tableId: string, colIds: string = '*'
250+
): Promise<number | undefined> {
251+
const table = await api.getTable(docId, '_grist_ACLResources');
252+
const index = table.tableId.indexOf(tableId);
253+
// Returns undefined if index is -1
254+
return table.id[index];
255+
}
256+
257+
async function blockTable(api: UserAPI, docId: string, tableId: string) {
258+
await api.applyUserActions(docId, [
259+
['AddRecord', '_grist_ACLResources', 2, {tableId: tableId, colIds: '*'}],
260+
['AddRecord', '_grist_ACLRules', null, {
261+
resource: 2, aclFormula: 'user.Access != "owners"', permissionsText: '-R',
262+
}],
263+
]);
264+
}
265+
266+
async function hideTable(api: UserAPI, docId: string, tableId: string) {
267+
const resourceId = await getTableResourceAclId(api, docId, tableId);
268+
await api.applyUserActions(docId, [
269+
['AddRecord', '_grist_ACLRules', null, {
270+
resource: resourceId, aclFormula: 'True', permissionsText: '-R',
271+
}],
272+
]);
273+
}
274+
275+
async function goToDocUsage() {
276+
await driver.findWait('.test-tools-raw', 2000).click();
277+
278+
// Check that the Usage section exists.
279+
await waitForDocUsage();
280+
}
281+
282+
async function assertUsageMessage(text: string | null) {
283+
if (text === null) {
284+
assert.isFalse(await driver.find('.test-doc-usage-message').isPresent());
285+
} else {
286+
assert.equal(await driver.findWait('.test-doc-usage-message-text', 2000).getText(), text);
287+
}
288+
}
289+
290+
const USAGE_ACCESS_DENIED_TEXT = 'Usage statistics are only available to users with full access to the document data.';
291+
async function assertUsageAccessDenied() {
292+
await assertUsageMessage(USAGE_ACCESS_DENIED_TEXT);
293+
assert.isFalse(await driver.find('.test-doc-usage-metrics').isPresent());
294+
}
295+
296+
async function assertUsageAccessAllowed() {
297+
await assert.isRejected(driver.findWait('.test-doc-usage-message-text', 2000));
298+
assert.isTrue(await driver.find('.test-doc-usage-metrics').isPresent());
299+
}
300+
301+
async function assertRowCount(currentValue: string, maximumValue?: string) {
302+
await gu.waitToPass(async () => {
303+
const rowUsage = await driver.find('.test-doc-usage-rows .test-doc-usage-value').getText();
304+
const [, foundValue, foundMax] = rowUsage.match(/([0-9,]+) (?:of ([0-9,]+) )?rows/) || [];
305+
assert.equal(foundValue, currentValue);
306+
if (maximumValue) {
307+
assert.equal(foundMax, maximumValue);
308+
}
309+
});
310+
}
311+
312+
async function assertDataSize(currentValue: string, maximumValue?: string) {
313+
await gu.waitToPass(async () => {
314+
const dataUsage = await driver.find('.test-doc-usage-data-size .test-doc-usage-value').getText();
315+
const [, foundValue, foundMax] = dataUsage.match(/([0-9,.]+) (?:of ([0-9,.]+) )?MB/) || [];
316+
assert.equal(foundValue, currentValue);
317+
if (maximumValue) {
318+
assert.equal(foundMax, maximumValue);
319+
}
320+
});
321+
}
322+
323+
async function assertAttachmentsSize(currentValue: string, maximumValue?: string) {
324+
await gu.waitToPass(async () => {
325+
const attachmentUsage = await driver.find('.test-doc-usage-attachments-size .test-doc-usage-value').getText();
326+
const [, foundValue, foundMax] = attachmentUsage.match(/([0-9,.]+) (?:of ([0-9,.]+) )?GB/) || [];
327+
assert.equal(foundValue, currentValue);
328+
if (maximumValue) {
329+
assert.equal(foundMax, maximumValue);
330+
}
331+
});
332+
}
333+
334+
async function waitForDocUsage() {
335+
// Extended timeout from 2000 to 8000
336+
await driver.findWait('.test-doc-usage-container', 8000);
337+
await gu.waitToPass(async () => {
338+
return assert.isFalse(await driver.find('.test-doc-usage-loading').isPresent());
339+
});
340+
}
341+
342+
async function addAttachmentColumn(columnName: string) {
343+
await gu.toggleSidePanel('right', 'open');
344+
await driver.find('.test-right-tab-field').click();
345+
await gu.addColumn(columnName);
346+
await gu.setType(/Attachment/);
347+
}
348+
349+
async function removeUnusedAttachments(api: UserAPI, docId: string) {
350+
const headers = {Authorization: `Bearer ${await api.fetchApiKey()}`};
351+
const url = server.getUrl('docs', `/api/docs/${docId}`);
352+
await fetch(url + "/attachments/removeUnused?verifyfiles=0&expiredonly=0", {
353+
headers,
354+
method: "POST"
355+
});
356+
}

0 commit comments

Comments
 (0)