mirror of
https://github.com/gristlabs/grist-core.git
synced 2024-10-27 20:44:07 +00:00
3e70a77729
Summary: These are tests that we just never moved into the public repo. It's just a small chore to make them public. Test Plan: Make sure the tests still pass Reviewers: jarek Reviewed By: jarek Differential Revision: https://phab.getgrist.com/D4311
418 lines
18 KiB
TypeScript
418 lines
18 KiB
TypeScript
import {UserProfile} from 'app/common/LoginSessionAPI';
|
|
import {HomeDBManager} from 'app/gen-server/lib/homedb/HomeDBManager';
|
|
import {FREE_PLAN, STUB_PLAN, TEAM_PLAN} from 'app/common/Features';
|
|
import {assert} from 'chai';
|
|
import {TestServer} from 'test/gen-server/apiUtils';
|
|
import * as testUtils from 'test/server/testUtils';
|
|
import uuidv4 from 'uuid/v4';
|
|
import omit = require('lodash/omit');
|
|
|
|
const charonProfile = {email: 'charon@getgrist.com', name: 'Charon'};
|
|
const chimpyProfile = {email: 'chimpy@getgrist.com', name: 'Chimpy'};
|
|
const kiwiProfile = {email: 'kiwi@getgrist.com', name: 'Kiwi'};
|
|
|
|
const teamOptions = {
|
|
setUserAsOwner: false, useNewPlan: true, product: TEAM_PLAN
|
|
};
|
|
|
|
describe('HomeDBManager', function() {
|
|
|
|
let server: TestServer;
|
|
let home: HomeDBManager;
|
|
testUtils.setTmpLogLevel('error');
|
|
|
|
before(async function() {
|
|
server = new TestServer(this);
|
|
await server.start();
|
|
home = server.dbManager;
|
|
});
|
|
|
|
after(async function() {
|
|
await server.stop();
|
|
});
|
|
|
|
it('can find existing user by email', async function() {
|
|
const user = await home.getUserByLogin('chimpy@getgrist.com');
|
|
assert.equal(user!.name, 'Chimpy');
|
|
});
|
|
|
|
it('can create new user by email, with personal org', async function() {
|
|
const profile = {email: 'unseen@getgrist.com', name: 'Unseen'};
|
|
const user = await home.getUserByLogin('unseen@getgrist.com', {profile});
|
|
assert.equal(user!.name, 'Unseen');
|
|
const orgs = await home.getOrgs(user!.id, null);
|
|
assert.isAtLeast(orgs.data!.length, 1);
|
|
assert.equal(orgs.data![0].name, 'Personal');
|
|
assert.equal(orgs.data![0].owner.name, 'Unseen');
|
|
});
|
|
|
|
it('parallel requests resulting in user creation give consistent results', async function() {
|
|
const profile = {
|
|
email: uuidv4() + "@getgrist.com",
|
|
name: "Testy McTestyTest"
|
|
};
|
|
const queries = [];
|
|
for (let i = 0; i < 100; i++) {
|
|
queries.push(home.getUserByLoginWithRetry(profile.email, {profile}));
|
|
}
|
|
const result = await Promise.all(queries);
|
|
const refUser = result[0];
|
|
assert(refUser && refUser.personalOrg && refUser.id && refUser.personalOrg.id);
|
|
result.forEach((user) => assert.deepEqual(refUser, user));
|
|
});
|
|
|
|
it('can accumulate profile information', async function() {
|
|
// log in without a name
|
|
let user = await home.getUserByLogin('unseen2@getgrist.com');
|
|
// name is blank
|
|
assert.equal(user!.name, '');
|
|
// log in with a name
|
|
const profile: UserProfile = {email: 'unseen2@getgrist.com', name: 'Unseen2'};
|
|
user = await home.getUserByLogin('unseen2@getgrist.com', {profile});
|
|
// name is now set
|
|
assert.equal(user!.name, 'Unseen2');
|
|
// log in without a name
|
|
user = await home.getUserByLogin('unseen2@getgrist.com');
|
|
// name is still set
|
|
assert.equal(user!.name, 'Unseen2');
|
|
// no picture yet
|
|
assert.equal(user!.picture, null);
|
|
// log in with picture link
|
|
profile.picture = 'http://picture.pic';
|
|
user = await home.getUserByLogin('unseen2@getgrist.com', {profile});
|
|
// now should have a picture link
|
|
assert.equal(user!.picture, 'http://picture.pic');
|
|
// log in without picture
|
|
user = await home.getUserByLogin('unseen2@getgrist.com');
|
|
// should still have picture link
|
|
assert.equal(user!.picture, 'http://picture.pic');
|
|
});
|
|
|
|
it('can add an org', async function() {
|
|
const user = await home.getUserByLogin('chimpy@getgrist.com');
|
|
const orgId = (await home.addOrg(user!, {name: 'NewOrg', domain: 'novel-org'}, teamOptions)).data!;
|
|
const org = await home.getOrg({userId: user!.id}, orgId);
|
|
assert.equal(org.data!.name, 'NewOrg');
|
|
assert.equal(org.data!.domain, 'novel-org');
|
|
assert.equal(org.data!.billingAccount.product.name, TEAM_PLAN);
|
|
await home.deleteOrg({userId: user!.id}, orgId);
|
|
});
|
|
|
|
it('creates default plan if defined', async function() {
|
|
const user = await home.getUserByLogin('chimpy@getgrist.com');
|
|
const oldEnv = new testUtils.EnvironmentSnapshot();
|
|
try {
|
|
// Set the default product to be the free plan.
|
|
process.env.GRIST_DEFAULT_PRODUCT = FREE_PLAN;
|
|
let orgId = (await home.addOrg(user!, {name: 'NewOrg', domain: 'novel-org'}, {
|
|
setUserAsOwner: false,
|
|
useNewPlan: true,
|
|
// omit plan, to use a default one (teamInitial)
|
|
// it will either be 'stub' or anything set in GRIST_DEFAULT_PRODUCT
|
|
})).data!;
|
|
let org = await home.getOrg({userId: user!.id}, orgId);
|
|
assert.equal(org.data!.name, 'NewOrg');
|
|
assert.equal(org.data!.domain, 'novel-org');
|
|
assert.equal(org.data!.billingAccount.product.name, FREE_PLAN);
|
|
await home.deleteOrg({userId: user!.id}, orgId);
|
|
|
|
// Now remove the default product, and check that the default plan is used.
|
|
delete process.env.GRIST_DEFAULT_PRODUCT;
|
|
orgId = (await home.addOrg(user!, {name: 'NewOrg', domain: 'novel-org'}, {
|
|
setUserAsOwner: false,
|
|
useNewPlan: true,
|
|
})).data!;
|
|
|
|
org = await home.getOrg({userId: user!.id}, orgId);
|
|
assert.equal(org.data!.billingAccount.product.name, STUB_PLAN);
|
|
await home.deleteOrg({userId: user!.id}, orgId);
|
|
} finally {
|
|
oldEnv.restore();
|
|
}
|
|
});
|
|
|
|
it('cannot duplicate a domain', async function() {
|
|
const user = await home.getUserByLogin('chimpy@getgrist.com');
|
|
const domain = 'repeated-domain';
|
|
const result = await home.addOrg(user!, {name: `${domain}!`, domain}, teamOptions);
|
|
const orgId = result.data!;
|
|
assert.equal(result.status, 200);
|
|
await assert.isRejected(home.addOrg(user!, {name: `${domain}!`, domain}, teamOptions),
|
|
/Domain already in use/);
|
|
await home.deleteOrg({userId: user!.id}, orgId);
|
|
});
|
|
|
|
it('cannot add an org with a (blacklisted) dodgy domain', async function() {
|
|
const user = await home.getUserByLogin('chimpy@getgrist.com');
|
|
const userId = user!.id;
|
|
const misses = [
|
|
'thing!', ' thing', 'ww', 'docs-999', 'o-99', '_domainkey', 'www', 'api',
|
|
'thissubdomainiswaytoolongmyfriendyoushouldrethinkitoratleastsummarizeit',
|
|
'google', 'login', 'doc-worker-1-1-1-1', 'a', 'bb', 'x_y', '1ogin'
|
|
];
|
|
const hits = [
|
|
'thing', 'jpl', 'xyz', 'appel', '123', '1google'
|
|
];
|
|
for (const domain of misses) {
|
|
const result = await home.addOrg(user!, {name: `${domain}!`, domain}, teamOptions);
|
|
assert.equal(result.status, 400);
|
|
const org = await home.getOrg({userId}, domain);
|
|
assert.equal(org.status, 404);
|
|
}
|
|
for (const domain of hits) {
|
|
const result = await home.addOrg(user!, {name: `${domain}!`, domain}, teamOptions);
|
|
assert.equal(result.status, 200);
|
|
const org = await home.getOrg({userId}, domain);
|
|
assert.equal(org.status, 200);
|
|
await home.deleteOrg({userId}, org.data!.id);
|
|
}
|
|
});
|
|
|
|
it('should allow setting doc metadata', async function() {
|
|
const beforeRun = new Date();
|
|
const setDateISO1 = new Date(Date.UTC(1993, 3, 2)).toISOString();
|
|
const setDateISO2 = new Date(Date.UTC(2004, 6, 18)).toISOString();
|
|
const setUsage1 = {rowCount: {total: 123}, dataSizeBytes: 456, attachmentsSizeBytes: 789};
|
|
const setUsage2 = {rowCount: {total: 0}, attachmentsSizeBytes: 0};
|
|
|
|
// Set the doc updatedAt time on Bananas.
|
|
const primatelyOrgId = await home.testGetId('Primately') as number;
|
|
const fishOrgId = await home.testGetId('Fish') as number;
|
|
const applesDocId = await home.testGetId('Apples') as string;
|
|
const bananasDocId = await home.testGetId('Bananas') as string;
|
|
const sharkDocId = await home.testGetId('Shark') as string;
|
|
await home.setDocsMetadata({
|
|
[applesDocId]: {usage: setUsage1},
|
|
[bananasDocId]: {updatedAt: setDateISO1},
|
|
[sharkDocId]: {updatedAt: setDateISO2, usage: setUsage2},
|
|
});
|
|
|
|
// Fetch the doc and check that the updatedAt value is as expected.
|
|
const kiwi = await home.getUserByLogin('kiwi@getgrist.com');
|
|
const resp1 = await home.getOrgWorkspaces({userId: kiwi!.id}, primatelyOrgId);
|
|
assert.equal(resp1.status, 200);
|
|
|
|
// Check that the apples metadata is as expected. updatedAt should have been set
|
|
// when the db was initialized before the update run - it should not have been updated
|
|
// to 1993. usage should be set.
|
|
const apples = resp1.data![0].docs.find((doc: any) => doc.name === 'Apples');
|
|
const applesUpdate = new Date(apples!.updatedAt);
|
|
assert.isTrue(applesUpdate < beforeRun);
|
|
assert.isTrue(applesUpdate > new Date('2000-1-1'));
|
|
assert.deepEqual(apples!.usage, setUsage1);
|
|
|
|
// Check that the bananas metadata is as expected. updatedAt should have been set
|
|
// to 1993. usage should be null.
|
|
const bananas = resp1.data![0].docs.find((doc: any) => doc.name === 'Bananas');
|
|
assert.equal(bananas!.updatedAt.toISOString(), setDateISO1);
|
|
assert.equal(bananas!.usage, null);
|
|
|
|
// Check that the shark metadata is as expected. updatedAt should have been set
|
|
// to 2004. usage should be set.
|
|
const resp2 = await home.getOrgWorkspaces({userId: kiwi!.id}, fishOrgId);
|
|
assert.equal(resp2.status, 200);
|
|
const shark = resp2.data![0].docs.find((doc: any) => doc.name === 'Shark');
|
|
assert.equal(shark!.updatedAt.toISOString(), setDateISO2);
|
|
assert.deepEqual(shark!.usage, setUsage2);
|
|
});
|
|
|
|
it("can pool orgs for two users", async function() {
|
|
const charonOrgs = (await home.getOrgs([charonProfile], null)).data!;
|
|
const kiwiOrgs = (await home.getOrgs([kiwiProfile], null)).data!;
|
|
const pooledOrgs = (await home.getOrgs([charonProfile, kiwiProfile], null)).data!;
|
|
// test there is some overlap
|
|
assert.isAbove(pooledOrgs.length, charonOrgs.length);
|
|
assert.isAbove(pooledOrgs.length, kiwiOrgs.length);
|
|
assert.isBelow(pooledOrgs.length, charonOrgs.length + kiwiOrgs.length);
|
|
// check specific orgs returned
|
|
assert.sameDeepMembers(charonOrgs.map(org => org.name),
|
|
['Abyss', 'Fish', 'NASA', 'Charonland', 'Chimpyland']);
|
|
assert.sameDeepMembers(kiwiOrgs.map(org => org.name),
|
|
['Fish', 'Flightless', 'Kiwiland', 'Primately']);
|
|
assert.sameDeepMembers(pooledOrgs.map(org => org.name),
|
|
['Abyss', 'Fish', 'Flightless', 'NASA', 'Primately', 'Charonland', 'Chimpyland', 'Kiwiland']);
|
|
|
|
// make sure if there are no profiles that we get no orgs
|
|
const emptyOrgs = (await home.getOrgs([], null)).data!;
|
|
assert.lengthOf(emptyOrgs, 0);
|
|
});
|
|
|
|
it("can pool orgs for three users", async function() {
|
|
const pooledOrgs = (await home.getOrgs([charonProfile, chimpyProfile, kiwiProfile], null)).data!;
|
|
assert.sameDeepMembers(pooledOrgs.map(org => org.name), [
|
|
'Abyss',
|
|
'EmptyOrg',
|
|
'EmptyWsOrg',
|
|
'Fish',
|
|
'Flightless',
|
|
'FreeTeam',
|
|
'NASA',
|
|
'Primately',
|
|
'TestDailyApiLimit',
|
|
'Charonland',
|
|
'Chimpyland',
|
|
'Kiwiland',
|
|
]);
|
|
});
|
|
|
|
it("can pool orgs for multiple users with non-normalized emails", async function() {
|
|
const refOrgs = (await home.getOrgs([charonProfile, kiwiProfile], null)).data!;
|
|
// Profiles in sessions can have email addresses with arbitrary capitalization.
|
|
const oddCharonProfile = {email: 'CharON@getgrist.COM', name: 'charON'};
|
|
const oddKiwiProfile = {email: 'KIWI@getgrist.COM', name: 'KIwi'};
|
|
const orgs = (await home.getOrgs([oddCharonProfile, kiwiProfile, oddKiwiProfile], null)).data!;
|
|
assert.deepEqual(refOrgs, orgs);
|
|
});
|
|
|
|
it('can get best user for accessing org', async function() {
|
|
let suggestion = await home.getBestUserForOrg([charonProfile, kiwiProfile],
|
|
await home.testGetId('Fish') as number);
|
|
assert.deepEqual(suggestion, {
|
|
id: await home.testGetId('Kiwi') as number,
|
|
email: kiwiProfile.email,
|
|
name: kiwiProfile.name,
|
|
access: 'editors',
|
|
perms: 15
|
|
});
|
|
suggestion = await home.getBestUserForOrg([charonProfile, kiwiProfile],
|
|
await home.testGetId('Abyss') as number);
|
|
assert.equal(suggestion!.email, charonProfile.email);
|
|
suggestion = await home.getBestUserForOrg([charonProfile, kiwiProfile],
|
|
await home.testGetId('EmptyOrg') as number);
|
|
assert.equal(suggestion, null);
|
|
});
|
|
|
|
it('skips picking a user for merged personal org', async function() {
|
|
// There isn't any particular way to favor one user over another when accessing
|
|
// the merged personal org.
|
|
assert.equal(await home.getBestUserForOrg([charonProfile, kiwiProfile], 0), null);
|
|
});
|
|
|
|
it('can access billingAccount for org', async function() {
|
|
await server.addBillingManager('Chimpy', 'nasa');
|
|
const chimpyScope = {userId: await home.testGetId('Chimpy') as number};
|
|
const charonScope = {userId: await home.testGetId('Charon') as number};
|
|
|
|
// billing account without orgs+managers
|
|
let billingAccount = await home.getBillingAccount(chimpyScope, 'nasa', false);
|
|
assert.hasAllKeys(billingAccount,
|
|
['id', 'individual', 'inGoodStanding', 'status', 'stripeCustomerId',
|
|
'stripeSubscriptionId', 'stripePlanId', 'product', 'paid', 'isManager',
|
|
'externalId', 'externalOptions', 'features', 'paymentLink']);
|
|
|
|
// billing account with orgs+managers
|
|
billingAccount = await home.getBillingAccount(chimpyScope, 'nasa', true);
|
|
assert.hasAllKeys(billingAccount,
|
|
['id', 'individual', 'inGoodStanding', 'status', 'stripeCustomerId',
|
|
'stripeSubscriptionId', 'stripePlanId', 'product', 'orgs', 'managers', /* <-- here */
|
|
'paid', 'externalId', 'externalOptions', 'features', 'paymentLink']);
|
|
|
|
await assert.isRejected(home.getBillingAccount(charonScope, 'nasa', true),
|
|
/User does not have access to billing account/);
|
|
});
|
|
|
|
// TypeORM does not handle parameter name reuse well, so we monkey-patch to detect it.
|
|
it('will fail on parameter collision', async function() {
|
|
// Check collision in a simple query.
|
|
// Note: it is query construction that fails, not query execution.
|
|
assert.throws(() => home.connection.createQueryBuilder().from('orgs', 'orgs')
|
|
.where('id = :id', {id: 1}).andWhere('id = :id', {id: 2}),
|
|
/parameter collision/);
|
|
|
|
// Check collision between subqueries.
|
|
assert.throws(
|
|
() => home.connection.createQueryBuilder().from('orgs', 'orgs')
|
|
.select(q => q.subQuery().from('orgs', 'orgs').where('x IN :x', {x: ['five']}))
|
|
.addSelect(q => q.subQuery().from('orgs', 'orgs').where('x IN :x', {x: ['six']})),
|
|
/parameter collision/);
|
|
});
|
|
|
|
it('can get the product associated with a docId', async function() {
|
|
const urlId = 'sampledocid_6';
|
|
const userId = await home.testGetId('Chimpy') as number;
|
|
const scope = {userId, urlId};
|
|
const doc = await home.getDoc(scope);
|
|
const product = (await home.getDocProduct(urlId))!;
|
|
assert.equal(doc.workspace.org.billingAccount.product.id, product.id);
|
|
const features = await home.getDocFeatures(urlId);
|
|
assert.deepEqual(features, {workspaces: true, vanityDomain: true});
|
|
});
|
|
|
|
it('can fork docs', async function() {
|
|
const user1 = await home.getUserByLogin('kiwi@getgrist.com');
|
|
const user1Id = user1!.id;
|
|
const orgId = await home.testGetId('Fish') as number;
|
|
const doc1Id = await home.testGetId('Shark') as string;
|
|
const scope = {userId: user1Id, urlId: doc1Id};
|
|
const doc1 = await home.getDoc(scope);
|
|
|
|
// Document "Shark" should initially have no forks.
|
|
const resp1 = await home.getOrgWorkspaces({userId: user1Id}, orgId);
|
|
const resp1Doc = resp1.data![0].docs.find((d: any) => d.name === 'Shark');
|
|
assert.deepEqual(resp1Doc!.forks, []);
|
|
|
|
// Fork "Shark" as Kiwi and check that their fork is listed.
|
|
const fork1Id = `${doc1Id}_fork_1`;
|
|
await home.forkDoc(user1Id, doc1, fork1Id);
|
|
const resp2 = await home.getOrgWorkspaces({userId: user1Id}, orgId);
|
|
const resp2Doc = resp2.data![0].docs.find((d: any) => d.name === 'Shark');
|
|
assert.deepEqual(
|
|
resp2Doc!.forks.map((fork: any) => omit(fork, 'updatedAt')),
|
|
[
|
|
{
|
|
id: fork1Id,
|
|
trunkId: doc1Id,
|
|
createdBy: user1Id,
|
|
options: null,
|
|
},
|
|
]
|
|
);
|
|
|
|
// Fork "Shark" again and check that Kiwi can see both forks.
|
|
const fork2Id = `${doc1Id}_fork_2`;
|
|
await home.forkDoc(user1Id, doc1, fork2Id);
|
|
const resp3 = await home.getOrgWorkspaces({userId: user1Id}, orgId);
|
|
const resp3Doc = resp3.data![0].docs.find((d: any) => d.name === 'Shark');
|
|
assert.sameDeepMembers(
|
|
resp3Doc!.forks.map((fork: any) => omit(fork, 'updatedAt')),
|
|
[
|
|
{
|
|
id: fork1Id,
|
|
trunkId: doc1Id,
|
|
createdBy: user1Id,
|
|
options: null,
|
|
},
|
|
{
|
|
id: fork2Id,
|
|
trunkId: doc1Id,
|
|
createdBy: user1Id,
|
|
options: null,
|
|
},
|
|
]
|
|
);
|
|
|
|
// Now fork "Shark" as Chimpy, and check that Kiwi's forks aren't listed.
|
|
const user2 = await home.getUserByLogin('chimpy@getgrist.com');
|
|
const user2Id = user2!.id;
|
|
const resp4 = await home.getOrgWorkspaces({userId: user2Id}, orgId);
|
|
const resp4Doc = resp4.data![0].docs.find((d: any) => d.name === 'Shark');
|
|
assert.deepEqual(resp4Doc!.forks, []);
|
|
|
|
const fork3Id = `${doc1Id}_fork_3`;
|
|
await home.forkDoc(user2Id, doc1, fork3Id);
|
|
const resp5 = await home.getOrgWorkspaces({userId: user2Id}, orgId);
|
|
const resp5Doc = resp5.data![0].docs.find((d: any) => d.name === 'Shark');
|
|
assert.deepEqual(
|
|
resp5Doc!.forks.map((fork: any) => omit(fork, 'updatedAt')),
|
|
[
|
|
{
|
|
id: fork3Id,
|
|
trunkId: doc1Id,
|
|
createdBy: user2Id,
|
|
options: null,
|
|
},
|
|
]
|
|
);
|
|
});
|
|
});
|