mirror of
https://github.com/gristlabs/grist-core.git
synced 2024-10-27 20:44:07 +00:00
554 lines
21 KiB
TypeScript
554 lines
21 KiB
TypeScript
|
import {ApiError} from 'app/common/ApiError';
|
||
|
import {Features} from 'app/common/Features';
|
||
|
import {resetOrg} from 'app/common/resetOrg';
|
||
|
import {UserAPI, UserAPIImpl} from 'app/common/UserAPI';
|
||
|
import {BillingAccount} from 'app/gen-server/entity/BillingAccount';
|
||
|
import {Organization} from 'app/gen-server/entity/Organization';
|
||
|
import {Product} from 'app/gen-server/entity/Product';
|
||
|
import {HomeDBManager} from 'app/gen-server/lib/homedb/HomeDBManager';
|
||
|
import {GristObjCode} from 'app/plugin/GristData';
|
||
|
import {assert} from 'chai';
|
||
|
import { IOptions } from 'app/common/BaseAPI';
|
||
|
import FormData from 'form-data';
|
||
|
import fetch from 'node-fetch';
|
||
|
import {TestServer} from 'test/gen-server/apiUtils';
|
||
|
import {configForUser, createUser} from 'test/gen-server/testUtils';
|
||
|
import * as testUtils from 'test/server/testUtils';
|
||
|
|
||
|
describe('limits', function() {
|
||
|
let home: TestServer;
|
||
|
let dbManager: HomeDBManager;
|
||
|
let homeUrl: string;
|
||
|
let product: Product;
|
||
|
let api: UserAPI;
|
||
|
let nasa: UserAPI;
|
||
|
|
||
|
testUtils.setTmpLogLevel('error');
|
||
|
|
||
|
before(async function() {
|
||
|
home = new TestServer(this);
|
||
|
await home.start(["home", "docs"]);
|
||
|
|
||
|
dbManager = home.dbManager;
|
||
|
homeUrl = home.serverUrl;
|
||
|
|
||
|
// Create a test product
|
||
|
product = new Product();
|
||
|
product.name = "test_product";
|
||
|
product.features = {workspaces: true};
|
||
|
await product.save();
|
||
|
// Create a new user
|
||
|
const samHome = await createUser(dbManager, 'sam');
|
||
|
// Overwrite default product
|
||
|
const billingId = samHome.billingAccount.id;
|
||
|
await dbManager.connection.createQueryBuilder()
|
||
|
.update(BillingAccount)
|
||
|
.set({product})
|
||
|
.where('id = :billingId', {billingId})
|
||
|
.execute();
|
||
|
// Set up an api object tied to the user's personal org
|
||
|
api = new UserAPIImpl(`${homeUrl}/o/docs`, {
|
||
|
fetch: fetch as any,
|
||
|
newFormData: () => new FormData() as any,
|
||
|
...configForUser('sam') as IOptions
|
||
|
});
|
||
|
// Give chimpy access to this org
|
||
|
await api.updateOrgPermissions('current', {users: {'chimpy@getgrist.com': 'owners'}});
|
||
|
// Set up an api object tied to nasa
|
||
|
nasa = new UserAPIImpl(`${homeUrl}/o/nasa`, {
|
||
|
fetch: fetch as any,
|
||
|
...configForUser('chimpy') as IOptions
|
||
|
});
|
||
|
});
|
||
|
|
||
|
after(async function() {
|
||
|
await home.stop();
|
||
|
});
|
||
|
|
||
|
async function setFeatures(features: Features) {
|
||
|
product.features = features;
|
||
|
await product.save();
|
||
|
}
|
||
|
|
||
|
it('can enforce limits on number of workspaces', async function() {
|
||
|
await setFeatures({maxWorkspacesPerOrg: 2, workspaces: true});
|
||
|
|
||
|
// initially have just one workspace, the default workspace
|
||
|
// created for a new personal org.
|
||
|
assert.lengthOf(await api.getOrgWorkspaces('current'), 1);
|
||
|
await assert.isFulfilled(api.newWorkspace({name: 'work2'}, 'current'));
|
||
|
await assert.isRejected(api.newWorkspace({name: 'work3'}, 'current'),
|
||
|
/No more workspaces/);
|
||
|
|
||
|
await setFeatures({maxWorkspacesPerOrg: 3, workspaces: true});
|
||
|
await assert.isFulfilled(api.newWorkspace({name: 'work3'}, 'current'));
|
||
|
await assert.isRejected(api.newWorkspace({name: 'work4'}, 'current'),
|
||
|
/No more workspaces/);
|
||
|
|
||
|
await setFeatures({workspaces: true});
|
||
|
await assert.isFulfilled(api.newWorkspace({name: 'work4'}, 'current'));
|
||
|
|
||
|
await setFeatures({maxWorkspacesPerOrg: 1, workspaces: true});
|
||
|
await assert.isRejected(api.newWorkspace({name: 'work5'}, 'current'),
|
||
|
/No more workspaces/);
|
||
|
});
|
||
|
|
||
|
it('can enforce limits on number of workspace shares', async function() {
|
||
|
this.timeout(4000);
|
||
|
await setFeatures({maxSharesPerWorkspace: 3, workspaces: true});
|
||
|
const wsId = await api.newWorkspace({name: 'work'}, 'docs');
|
||
|
|
||
|
// Adding 4 users would exceed 3 user limit
|
||
|
await assert.isRejected(api.updateWorkspacePermissions(wsId, {
|
||
|
users: {
|
||
|
'user1@getgrist.com': 'owners',
|
||
|
'user2@getgrist.com': 'viewers',
|
||
|
'user3@getgrist.com': 'owners',
|
||
|
'user4@getgrist.com': 'viewers',
|
||
|
}
|
||
|
}), /No more external workspace shares/);
|
||
|
|
||
|
// Adding 1 user is ok
|
||
|
await assert.isFulfilled(api.updateWorkspacePermissions(wsId, {
|
||
|
users: {'user1@getgrist.com': 'owners'}
|
||
|
}));
|
||
|
|
||
|
// Adding 2nd+3rd user is ok
|
||
|
await assert.isFulfilled(api.updateWorkspacePermissions(wsId, {
|
||
|
users: {
|
||
|
'user2@getgrist.com': 'owners',
|
||
|
'user3@getgrist.com': 'owners'
|
||
|
}
|
||
|
}));
|
||
|
|
||
|
// Adding 4th user fails
|
||
|
await assert.isRejected(api.updateWorkspacePermissions(wsId, {
|
||
|
users: {'user4@getgrist.com': 'owners'}
|
||
|
}), /No more external workspace shares/);
|
||
|
|
||
|
// Adding support user is ok
|
||
|
await assert.isFulfilled(api.updateWorkspacePermissions(wsId, {
|
||
|
users: {'support@getgrist.com': 'owners'}
|
||
|
}));
|
||
|
|
||
|
// Replacing user is ok
|
||
|
await assert.isFulfilled(api.updateWorkspacePermissions(wsId, {
|
||
|
users: {
|
||
|
'user2@getgrist.com': null,
|
||
|
'user2b@getgrist.com': 'owners'
|
||
|
}
|
||
|
}));
|
||
|
|
||
|
// Removing a user and adding another is ok
|
||
|
await assert.isFulfilled(api.updateWorkspacePermissions(wsId, {
|
||
|
users: {'user1@getgrist.com': null}
|
||
|
}));
|
||
|
await assert.isFulfilled(api.updateWorkspacePermissions(wsId, {
|
||
|
users: {'user1b@getgrist.com': 'owners'}
|
||
|
}));
|
||
|
await assert.isRejected(api.updateWorkspacePermissions(wsId, {
|
||
|
users: {'user5@getgrist.com': 'owners'}
|
||
|
}), /No more external workspace shares/);
|
||
|
|
||
|
// Reduce to limit to allow just one share
|
||
|
await setFeatures({maxSharesPerWorkspace: 1, workspaces: true});
|
||
|
|
||
|
// Cannot add or replace users, since we are over limit
|
||
|
await assert.isRejected(api.updateWorkspacePermissions(wsId, {
|
||
|
users: {
|
||
|
'user3@getgrist.com': null,
|
||
|
'user3b@getgrist.com': 'owners'
|
||
|
}
|
||
|
}), /No more external workspace shares/);
|
||
|
|
||
|
// Can remove a user, while still being over limit
|
||
|
await assert.isFulfilled(api.updateWorkspacePermissions(wsId, {
|
||
|
users: {'user1b@getgrist.com': null}
|
||
|
}));
|
||
|
await assert.isFulfilled(api.updateWorkspacePermissions(wsId, {
|
||
|
users: {'user2b@getgrist.com': null}
|
||
|
}));
|
||
|
await assert.isFulfilled(api.updateWorkspacePermissions(wsId, {
|
||
|
users: {'user3@getgrist.com': null}
|
||
|
}));
|
||
|
|
||
|
// Finally ok to add a user again
|
||
|
await assert.isFulfilled(api.updateWorkspacePermissions(wsId, {
|
||
|
users: {'user1@getgrist.com': 'owners'}
|
||
|
}));
|
||
|
});
|
||
|
|
||
|
it('can enforce limits on number of docs', async function() {
|
||
|
await setFeatures({maxDocsPerOrg: 2, workspaces: true});
|
||
|
const wsId = await api.newWorkspace({name: 'work'}, 'docs');
|
||
|
|
||
|
await assert.isFulfilled(api.newDoc({name: 'doc1'}, wsId));
|
||
|
await assert.isFulfilled(api.newDoc({name: 'doc2'}, wsId));
|
||
|
await assert.isRejected(api.newDoc({name: 'doc3'}, wsId), /No more documents/);
|
||
|
|
||
|
await setFeatures({maxDocsPerOrg: 3, workspaces: true});
|
||
|
await assert.isFulfilled(api.newDoc({name: 'doc3'}, wsId));
|
||
|
await assert.isRejected(api.newDoc({name: 'doc4'}, wsId), /No more documents/);
|
||
|
|
||
|
await setFeatures({workspaces: true});
|
||
|
await assert.isFulfilled(api.newDoc({name: 'doc4'}, wsId));
|
||
|
|
||
|
await setFeatures({maxDocsPerOrg: 1, workspaces: true});
|
||
|
await assert.isRejected(api.newDoc({name: 'doc5'}, wsId), /No more documents/);
|
||
|
|
||
|
// check that smuggling in a document from another org doesn't work.
|
||
|
await assert.isRejected(nasa.moveDoc(await dbManager.testGetId('Jupiter') as string, wsId),
|
||
|
/No more documents/);
|
||
|
|
||
|
// now make space for the document and try again.
|
||
|
await setFeatures({maxDocsPerOrg: 6, workspaces: true});
|
||
|
await assert.isFulfilled(nasa.moveDoc(await dbManager.testGetId('Jupiter') as string, wsId));
|
||
|
|
||
|
// add a document in a workspace we are then going to make inaccessible.
|
||
|
const wsHiddenId = await api.newWorkspace({name: 'hidden'}, 'docs');
|
||
|
await assert.isFulfilled(api.newDoc({name: 'doc6'}, wsHiddenId));
|
||
|
await assert.isRejected(api.newDoc({name: 'doc7'}, wsHiddenId), /No more documents/);
|
||
|
|
||
|
// transfer workspace ownership, and make inaccessible.
|
||
|
await api.updateWorkspacePermissions(wsHiddenId, {users: {'charon@getgrist.com': 'owners'}});
|
||
|
const charon = await home.createHomeApi('charon', 'docs', true);
|
||
|
await charon.updateWorkspacePermissions(wsHiddenId, {maxInheritedRole: null});
|
||
|
|
||
|
// now try adding a document and make sure it is denied.
|
||
|
await assert.isRejected(api.newDoc({name: 'doc7'}, wsId), /No more documents/);
|
||
|
|
||
|
// clean up workspace.
|
||
|
await charon.deleteWorkspace(wsHiddenId);
|
||
|
});
|
||
|
|
||
|
it('can enforce limits on number of doc shares', async function() {
|
||
|
this.timeout(4000); // This can exceed the default of 2s on Jenkins
|
||
|
|
||
|
await setFeatures({maxSharesPerDoc: 3, workspaces: true});
|
||
|
const wsId = await api.newWorkspace({name: 'shares'}, 'docs');
|
||
|
const docId = await api.newDoc({name: 'doc'}, wsId);
|
||
|
|
||
|
// Adding 4 users would exceed 3 user limit
|
||
|
await assert.isRejected(api.updateDocPermissions(docId, {
|
||
|
users: {
|
||
|
'user1@getgrist.com': 'owners',
|
||
|
'user2@getgrist.com': 'viewers',
|
||
|
'user3@getgrist.com': 'owners',
|
||
|
'user4@getgrist.com': 'viewers',
|
||
|
}
|
||
|
}), /No more external document shares/);
|
||
|
|
||
|
// Adding 1 user is ok
|
||
|
await assert.isFulfilled(api.updateDocPermissions(docId, {
|
||
|
users: {'user1@getgrist.com': 'owners'}
|
||
|
}));
|
||
|
|
||
|
// Adding 2nd+3rd user is ok
|
||
|
await assert.isFulfilled(api.updateDocPermissions(docId, {
|
||
|
users: {
|
||
|
'user2@getgrist.com': 'owners',
|
||
|
'user3@getgrist.com': 'owners'
|
||
|
}
|
||
|
}));
|
||
|
|
||
|
// Adding 4th user fails
|
||
|
await assert.isRejected(api.updateDocPermissions(docId, {
|
||
|
users: {'user4@getgrist.com': 'owners'}
|
||
|
}), /No more external document shares/);
|
||
|
|
||
|
// Adding support user is ok
|
||
|
await assert.isFulfilled(api.updateDocPermissions(docId, {
|
||
|
users: {'support@getgrist.com': 'owners'}
|
||
|
}));
|
||
|
|
||
|
// Replacing user is ok
|
||
|
await assert.isFulfilled(api.updateDocPermissions(docId, {
|
||
|
users: {
|
||
|
'user2@getgrist.com': null,
|
||
|
'user2b@getgrist.com': 'owners'
|
||
|
}
|
||
|
}));
|
||
|
|
||
|
// Removing a user and adding another is ok
|
||
|
await assert.isFulfilled(api.updateDocPermissions(docId, {
|
||
|
users: {'user1@getgrist.com': null}
|
||
|
}));
|
||
|
await assert.isFulfilled(api.updateDocPermissions(docId, {
|
||
|
users: {'user1b@getgrist.com': 'owners'}
|
||
|
}));
|
||
|
await assert.isRejected(api.updateDocPermissions(docId, {
|
||
|
users: {'user5@getgrist.com': 'owners'}
|
||
|
}), /No more external document shares/);
|
||
|
|
||
|
// Reduce to limit to allow just one share
|
||
|
await setFeatures({maxSharesPerDoc: 1, workspaces: true});
|
||
|
|
||
|
// Cannot add or replace users, since we are over limit
|
||
|
await assert.isRejected(api.updateDocPermissions(docId, {
|
||
|
users: {
|
||
|
'user3@getgrist.com': null,
|
||
|
'user3b@getgrist.com': 'owners'
|
||
|
}
|
||
|
}), /No more external document shares/);
|
||
|
|
||
|
// Can remove a user, while still being over limit
|
||
|
await assert.isFulfilled(api.updateDocPermissions(docId, {
|
||
|
users: {'user1b@getgrist.com': null}
|
||
|
}));
|
||
|
await assert.isFulfilled(api.updateDocPermissions(docId, {
|
||
|
users: {'user2b@getgrist.com': null}
|
||
|
}));
|
||
|
await assert.isFulfilled(api.updateDocPermissions(docId, {
|
||
|
users: {'user3@getgrist.com': null}
|
||
|
}));
|
||
|
|
||
|
// Finally ok to add a user again
|
||
|
await assert.isFulfilled(api.updateDocPermissions(docId, {
|
||
|
users: {'user1@getgrist.com': 'owners'}
|
||
|
}));
|
||
|
|
||
|
// Try smuggling in a doc that breaks the rules
|
||
|
// Tweak NASA's product to allow 4 shares per doc.
|
||
|
const db = dbManager.connection.manager;
|
||
|
const nasaOrg = await db.findOne(Organization, {where: {domain: 'nasa'},
|
||
|
relations: ['billingAccount',
|
||
|
'billingAccount.product']});
|
||
|
if (!nasaOrg) { throw new Error('could not find nasa org'); }
|
||
|
const nasaProduct = nasaOrg.billingAccount.product;
|
||
|
const originalFeatures = nasaProduct.features;
|
||
|
|
||
|
nasaProduct.features = {...originalFeatures, maxSharesPerDoc: 4};
|
||
|
await nasaProduct.save();
|
||
|
|
||
|
const pluto = await dbManager.testGetId('Pluto') as string;
|
||
|
await nasa.updateDocPermissions(pluto, {
|
||
|
users: {
|
||
|
'zig@getgrist.com': 'owners',
|
||
|
'zag@getgrist.com': 'editors',
|
||
|
'zog@getgrist.com': 'viewers',
|
||
|
}
|
||
|
});
|
||
|
await assert.isRejected(nasa.moveDoc(pluto, wsId), /Too many external document shares/);
|
||
|
|
||
|
// Increase the limit and try again
|
||
|
await setFeatures({maxSharesPerDoc: 100, workspaces: true});
|
||
|
await assert.isFulfilled(nasa.moveDoc(pluto, wsId));
|
||
|
});
|
||
|
|
||
|
it('can enforce limits on number of doc shares per role', async function() {
|
||
|
this.timeout(4000); // This can exceed the default of 2s on Jenkins
|
||
|
|
||
|
await setFeatures({maxSharesPerDoc: 10,
|
||
|
maxSharesPerDocPerRole: {
|
||
|
owners: 1,
|
||
|
editors: 2
|
||
|
},
|
||
|
workspaces: true});
|
||
|
const wsId = await api.newWorkspace({name: 'roleShares'}, 'docs');
|
||
|
const docId = await api.newDoc({name: 'doc'}, wsId);
|
||
|
|
||
|
// can add plenty of viewers
|
||
|
await assert.isFulfilled(api.updateDocPermissions(docId, {
|
||
|
users: {
|
||
|
'viewer1@getgrist.com': 'viewers',
|
||
|
'viewer2@getgrist.com': 'viewers',
|
||
|
'viewer3@getgrist.com': 'viewers',
|
||
|
'viewer4@getgrist.com': 'viewers'
|
||
|
}
|
||
|
}));
|
||
|
|
||
|
// can add just one owner
|
||
|
await assert.isFulfilled(api.updateDocPermissions(docId, {
|
||
|
users: {'owner1@getgrist.com': 'owners'}
|
||
|
}));
|
||
|
await assert.isRejected(api.updateDocPermissions(docId, {
|
||
|
users: {'owner2@getgrist.com': 'owners'}
|
||
|
}), /No more external document owners/);
|
||
|
|
||
|
// can add at most two editors
|
||
|
await assert.isRejected(api.updateDocPermissions(docId, {
|
||
|
users: {
|
||
|
'editor1@getgrist.com': 'editors',
|
||
|
'editor2@getgrist.com': 'editors',
|
||
|
'editor3@getgrist.com': 'editors'
|
||
|
}
|
||
|
}), /No more external document editors/);
|
||
|
await assert.isFulfilled(api.updateDocPermissions(docId, {
|
||
|
users: {
|
||
|
'editor1@getgrist.com': 'editors',
|
||
|
'editor2@getgrist.com': 'editors'
|
||
|
}
|
||
|
}));
|
||
|
|
||
|
// can convert an editor to a viewer and then add another editor
|
||
|
await assert.isFulfilled(api.updateDocPermissions(docId, {
|
||
|
users: {
|
||
|
'editor1@getgrist.com': 'viewers',
|
||
|
'editor3@getgrist.com': 'editors'
|
||
|
}
|
||
|
}));
|
||
|
|
||
|
// we are at 8 shares, can make just two more
|
||
|
await assert.isFulfilled(api.updateDocPermissions(docId, {
|
||
|
users: {'viewer5@getgrist.com': 'viewers'}
|
||
|
}));
|
||
|
await assert.isFulfilled(api.updateDocPermissions(docId, {
|
||
|
users: {'viewer6@getgrist.com': 'viewers'}
|
||
|
}));
|
||
|
await assert.isRejected(api.updateDocPermissions(docId, {
|
||
|
users: {'viewer7@getgrist.com': 'viewers'}
|
||
|
}), /No more external document shares/);
|
||
|
|
||
|
|
||
|
// Try smuggling in a doc that exceeds limits
|
||
|
const beyond = await dbManager.testGetId('Beyond') as string;
|
||
|
await nasa.updateDocPermissions(beyond, {
|
||
|
users: {
|
||
|
'zig@getgrist.com': 'owners',
|
||
|
'zag@getgrist.com': 'owners'
|
||
|
}
|
||
|
});
|
||
|
await assert.isRejected(nasa.moveDoc(beyond, wsId), /Too many external document owners/);
|
||
|
|
||
|
// Increase the limit and try again
|
||
|
await setFeatures({maxSharesPerDoc: 10,
|
||
|
maxSharesPerDocPerRole: {
|
||
|
owners: 2,
|
||
|
editors: 2
|
||
|
},
|
||
|
workspaces: true});
|
||
|
await assert.isFulfilled(nasa.moveDoc(beyond, wsId));
|
||
|
});
|
||
|
|
||
|
it('can give good tips when exceeding doc shares', async function() {
|
||
|
await setFeatures({maxSharesPerDoc: 2, workspaces: true});
|
||
|
const wsId = await api.newWorkspace({name: 'shares'}, 'docs');
|
||
|
const docId = await api.newDoc({name: 'doc'}, wsId);
|
||
|
|
||
|
await assert.isFulfilled(api.updateDocPermissions(docId, {
|
||
|
users: {
|
||
|
'user1@getgrist.com': 'owners',
|
||
|
'user2@getgrist.com': 'viewers',
|
||
|
}
|
||
|
}));
|
||
|
let err: ApiError = await api.updateDocPermissions(docId, {
|
||
|
users: {
|
||
|
'user3@getgrist.com': 'owners',
|
||
|
}
|
||
|
}).catch(e => e);
|
||
|
// Advice should be to add users as members.
|
||
|
assert.sameMembers(err.details!.tips!.map(tip => tip.action), ['add-members']);
|
||
|
|
||
|
// Now switch to a product that looks like a personal site
|
||
|
await setFeatures({maxSharesPerDoc: 2, workspaces: true, maxWorkspacesPerOrg: 1});
|
||
|
err = await api.updateDocPermissions(docId, {
|
||
|
users: {
|
||
|
'user3@getgrist.com': 'owners',
|
||
|
}
|
||
|
}).catch(e => e);
|
||
|
// Advice should be to upgrade.
|
||
|
assert.sameMembers(err.details!.tips!.map(tip => tip.action), ['upgrade']);
|
||
|
});
|
||
|
|
||
|
it('can give good tips when exceeding workspace shares', async function() {
|
||
|
await setFeatures({maxSharesPerWorkspace: 2, workspaces: true});
|
||
|
const wsId = await api.newWorkspace({name: 'shares'}, 'docs');
|
||
|
|
||
|
await assert.isFulfilled(api.updateWorkspacePermissions(wsId, {
|
||
|
users: {
|
||
|
'user1@getgrist.com': 'owners',
|
||
|
'user2@getgrist.com': 'viewers',
|
||
|
}
|
||
|
}));
|
||
|
let err: ApiError = await api.updateWorkspacePermissions(wsId, {
|
||
|
users: {
|
||
|
'user3@getgrist.com': 'owners',
|
||
|
}
|
||
|
}).catch(e => e);
|
||
|
// Advice should be to add users as members.
|
||
|
assert.sameMembers(err.details!.tips!.map(tip => tip.action), ['add-members']);
|
||
|
|
||
|
// Now switch to a product that looks like a personal site (it should not
|
||
|
// be possible to share workspaces via UI in this case though)
|
||
|
await setFeatures({maxSharesPerWorkspace: 0, workspaces: true, maxWorkspacesPerOrg: 1});
|
||
|
err = await api.updateWorkspacePermissions(wsId, {
|
||
|
users: {
|
||
|
'user3@getgrist.com': 'owners',
|
||
|
}
|
||
|
}).catch(e => e);
|
||
|
// Advice should be to upgrade.
|
||
|
assert.sameMembers(err.details!.tips!.map(tip => tip.action), ['upgrade']);
|
||
|
});
|
||
|
|
||
|
it('discounts deleted and soft-deleted documents from quota', async function() {
|
||
|
this.timeout(3000); // This can exceed the default of 2s on Jenkins
|
||
|
|
||
|
// Reset org to contain no docs, and set limit on docs to 2
|
||
|
await resetOrg(api, 'docs');
|
||
|
await setFeatures({maxDocsPerOrg: 2, workspaces: true});
|
||
|
const wsId = await api.newWorkspace({name: 'work'}, 'docs');
|
||
|
|
||
|
// Create 2 docs. Then creating another will fail.
|
||
|
const doc1 = await api.newDoc({name: 'doc1'}, wsId);
|
||
|
const doc2 = await api.newDoc({name: 'doc2'}, wsId);
|
||
|
await assert.isRejected(api.newDoc({name: 'doc3'}, wsId), /No more documents/);
|
||
|
|
||
|
// Hard-delete one doc, then we can add another.
|
||
|
await api.deleteDoc(doc1);
|
||
|
const doc3 = await api.newDoc({name: 'doc3'}, wsId);
|
||
|
|
||
|
// Soft-delete one doc, then we can add another.
|
||
|
await api.softDeleteDoc(doc2);
|
||
|
await api.newDoc({name: 'doc4'}, wsId);
|
||
|
|
||
|
// Check we can neither create nor recover a doc when full again.
|
||
|
await assert.isRejected(api.newDoc({name: 'doc5'}, wsId), /No more documents/);
|
||
|
await assert.isRejected(api.undeleteDoc(doc2), /No more documents/);
|
||
|
|
||
|
// Check that if we make some space we can recover a doc.
|
||
|
await api.softDeleteDoc(doc3);
|
||
|
await api.undeleteDoc(doc2);
|
||
|
});
|
||
|
|
||
|
it('can enforce limits on total attachment file size', async function() {
|
||
|
this.timeout(4000);
|
||
|
|
||
|
// Each attachment in this test will have one byte, so essentially we're limiting to two attachments
|
||
|
await setFeatures({baseMaxAttachmentsBytesPerDocument: 2});
|
||
|
|
||
|
const workspaces = await api.getOrgWorkspaces('current');
|
||
|
const docId = await api.newDoc({name: 'doc1'}, workspaces[0].id);
|
||
|
await api.applyUserActions(docId, [["ModifyColumn", "Table1", "A", {type: "Attachments"}]]);
|
||
|
const docApi = api.getDocAPI(docId);
|
||
|
|
||
|
// Add a cell referencing the attachments we're about to create.
|
||
|
// This ensures that they won't be immediately treated as soft-deleted and ignored in the total size calculation.
|
||
|
// Otherwise the uploads after this would succeed even if duplicate attachments were counted twice.
|
||
|
const rowIds = await docApi.addRows("Table1", {A: [[GristObjCode.List, 1, 2, 3]]});
|
||
|
assert.deepEqual(rowIds, [1]);
|
||
|
|
||
|
// We're limited to 2 attachments, but the attachment 'a' is duplicated so it's only counted once.
|
||
|
const attachmentIds = [
|
||
|
await docApi.uploadAttachment('a', 'a.txt'),
|
||
|
await docApi.uploadAttachment('a', 'a.txt'),
|
||
|
await docApi.uploadAttachment('b', 'b.txt'),
|
||
|
];
|
||
|
assert.deepEqual(attachmentIds, [1, 2, 3]);
|
||
|
|
||
|
// Now we're at the limit and trying to upload another attachment is rejected.
|
||
|
await assert.isRejected(docApi.uploadAttachment('c', 'c.txt'));
|
||
|
|
||
|
// Delete one reference to 'a', but there's still another one so we're still at the limit and can't upload more.
|
||
|
await docApi.updateRows("Table1", {id: rowIds, A: [[GristObjCode.List, 2, 3]]});
|
||
|
await assert.isRejected(docApi.uploadAttachment('c', 'c.txt'));
|
||
|
|
||
|
// Delete the other reference to 'a' so now there's only one referenced attachment 'b' and we can upload again.
|
||
|
await docApi.updateRows("Table1", {id: rowIds, A: [[GristObjCode.List, 3, 4]]});
|
||
|
assert.equal(await docApi.uploadAttachment('c', 'c.txt'), 4);
|
||
|
|
||
|
// Now we're at the limit again with 'b' and 'c' and can't upload further.
|
||
|
await assert.isRejected(docApi.uploadAttachment('d', 'd.txt'));
|
||
|
});
|
||
|
|
||
|
});
|