mirror of
https://github.com/gristlabs/grist-core.git
synced 2024-10-27 20:44:07 +00:00
Add tests
This commit is contained in:
parent
c38107aadc
commit
6f49f83b66
363
test/server/lib/Scim.ts
Normal file
363
test/server/lib/Scim.ts
Normal file
@ -0,0 +1,363 @@
|
||||
import axios from 'axios';
|
||||
import capitalize from 'lodash/capitalize';
|
||||
import { assert } from 'chai';
|
||||
import { TestServer } from 'test/gen-server/apiUtils';
|
||||
import { configForUser } from 'test/gen-server/testUtils';
|
||||
import * as testUtils from 'test/server/testUtils';
|
||||
|
||||
function scimConfigForUser(user: string) {
|
||||
const config = configForUser(user);
|
||||
return {
|
||||
...config,
|
||||
headers: {
|
||||
...config.headers,
|
||||
'Content-Type': 'application/scim+json'
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const chimpy = scimConfigForUser('Chimpy');
|
||||
const kiwi = scimConfigForUser('Kiwi');
|
||||
const anon = configForUser('Anonymous');
|
||||
|
||||
const USER_CONFIG_BY_NAME = {
|
||||
chimpy,
|
||||
kiwi,
|
||||
anon,
|
||||
};
|
||||
|
||||
type UserConfigByName = typeof USER_CONFIG_BY_NAME;
|
||||
|
||||
describe('Scim', () => {
|
||||
let oldEnv: testUtils.EnvironmentSnapshot;
|
||||
let server: TestServer;
|
||||
let homeUrl: string;
|
||||
const userIdByName: {[name in keyof UserConfigByName]?: number} = {};
|
||||
|
||||
const scimUrl = (path: string) => (homeUrl + '/api/scim/v2' + path);
|
||||
|
||||
before(async function () {
|
||||
oldEnv = new testUtils.EnvironmentSnapshot();
|
||||
process.env.GRIST_DEFAULT_EMAIL = 'chimpy@getgrist.com';
|
||||
process.env.TYPEORM_DATABASE = ':memory:';
|
||||
server = new TestServer(this);
|
||||
homeUrl = await server.start();
|
||||
const userNames = Object.keys(USER_CONFIG_BY_NAME) as Array<keyof UserConfigByName>;
|
||||
for (const user of userNames) {
|
||||
userIdByName[user] = await getUserId(user);
|
||||
}
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
oldEnv.restore();
|
||||
await server.stop();
|
||||
});
|
||||
|
||||
function personaToSCIMMYUserWithId(user: keyof UserConfigByName) {
|
||||
return toSCIMUserWithId(user, userIdByName[user]!);
|
||||
}
|
||||
|
||||
function toSCIMUserWithId(user: string, id: number) {
|
||||
return {
|
||||
...toSCIMUserWithoutId(user),
|
||||
id: String(id),
|
||||
meta: { resourceType: 'User', location: '/api/scim/v2/Users/' + id },
|
||||
};
|
||||
}
|
||||
|
||||
function toSCIMUserWithoutId(user: string) {
|
||||
return {
|
||||
schemas: [ 'urn:ietf:params:scim:schemas:core:2.0:User' ],
|
||||
userName: user + '@getgrist.com',
|
||||
name: { formatted: capitalize(user) },
|
||||
displayName: capitalize(user),
|
||||
preferredLanguage: 'en',
|
||||
locale: 'en',
|
||||
emails: [ { value: user + '@getgrist.com', primary: true } ]
|
||||
};
|
||||
}
|
||||
|
||||
async function getUserId(user: string) {
|
||||
return (await server.dbManager.getUserByLogin(user + '@getgrist.com'))!.id;
|
||||
}
|
||||
|
||||
function checkEndpointNotAccessibleForNonAdminUsers(
|
||||
method: 'get' | 'post' | 'put' | 'patch' | 'delete',
|
||||
path: string
|
||||
) {
|
||||
function makeCallWith(user: keyof UserConfigByName) {
|
||||
if (method === 'get' || method === 'delete') {
|
||||
return axios[method](scimUrl(path), USER_CONFIG_BY_NAME[user]);
|
||||
}
|
||||
return axios[method](scimUrl(path), {}, USER_CONFIG_BY_NAME[user]);
|
||||
}
|
||||
it('should return 401 for anonymous', async function () {
|
||||
const res: any = await makeCallWith('anon');
|
||||
assert.equal(res.status, 401);
|
||||
});
|
||||
|
||||
it('should return 401 for kiwi', async function () {
|
||||
const res: any = await makeCallWith('kiwi');
|
||||
assert.equal(res.status, 401);
|
||||
});
|
||||
}
|
||||
|
||||
describe('/Me', function () {
|
||||
async function checkGetMeAs(user: keyof UserConfigByName, expected: any) {
|
||||
const res = await axios.get(scimUrl('/Me'), USER_CONFIG_BY_NAME[user]);
|
||||
assert.equal(res.status, 200);
|
||||
assert.deepInclude(res.data, expected);
|
||||
}
|
||||
|
||||
it(`should return the current user for chimpy`, async function () {
|
||||
return checkGetMeAs('chimpy', personaToSCIMMYUserWithId('chimpy'));
|
||||
});
|
||||
|
||||
it(`should return the current user for kiwi`, async function () {
|
||||
return checkGetMeAs('kiwi', personaToSCIMMYUserWithId('kiwi'));
|
||||
});
|
||||
|
||||
it('should return 401 for anonymous', async function () {
|
||||
const res = await axios.get(scimUrl('/Me'), anon);
|
||||
assert.equal(res.status, 401);
|
||||
});
|
||||
});
|
||||
|
||||
describe('/Users/{id}', function () {
|
||||
|
||||
it('should return the user of id=1 for chimpy', async function () {
|
||||
const res = await axios.get(scimUrl('/Users/1'), chimpy);
|
||||
|
||||
assert.equal(res.status, 200);
|
||||
assert.deepInclude(res.data, {
|
||||
schemas: ['urn:ietf:params:scim:schemas:core:2.0:User'],
|
||||
id: '1',
|
||||
displayName: 'Chimpy',
|
||||
userName: 'chimpy@getgrist.com'
|
||||
});
|
||||
});
|
||||
|
||||
it('should return 404 when the user is not found', async function () {
|
||||
const res = await axios.get(scimUrl('/Users/1000'), chimpy);
|
||||
assert.equal(res.status, 404);
|
||||
assert.deepEqual(res.data, {
|
||||
schemas: [ 'urn:ietf:params:scim:api:messages:2.0:Error' ],
|
||||
status: '404',
|
||||
detail: 'User with ID 1000 not found'
|
||||
});
|
||||
});
|
||||
|
||||
checkEndpointNotAccessibleForNonAdminUsers('get', '/Users/1');
|
||||
});
|
||||
|
||||
describe('GET /Users', function () {
|
||||
it('should return all users for chimpy', async function () {
|
||||
const res = await axios.get(scimUrl('/Users'), chimpy);
|
||||
assert.equal(res.status, 200);
|
||||
assert.isAbove(res.data.totalResults, 0, 'should have retrieved some users');
|
||||
assert.deepInclude(res.data.Resources, personaToSCIMMYUserWithId('chimpy'));
|
||||
assert.deepInclude(res.data.Resources, personaToSCIMMYUserWithId('kiwi'));
|
||||
});
|
||||
|
||||
checkEndpointNotAccessibleForNonAdminUsers('get', '/Users');
|
||||
});
|
||||
|
||||
describe('POST /Users/.search', function () {
|
||||
const SEARCH_SCHEMA = 'urn:ietf:params:scim:api:messages:2.0:SearchRequest';
|
||||
it('should return all users for chimpy order by userName in descending order', async function () {
|
||||
const res = await axios.post(scimUrl('/Users/.search'), {
|
||||
schemas: [SEARCH_SCHEMA],
|
||||
sortBy: 'userName',
|
||||
sortOrder: 'descending',
|
||||
}, chimpy);
|
||||
assert.equal(res.status, 200);
|
||||
assert.isAbove(res.data.totalResults, 0, 'should have retrieved some users');
|
||||
const users = res.data.Resources.map((r: any) => r.userName);
|
||||
assert.include(users, 'chimpy@getgrist.com');
|
||||
assert.include(users, 'kiwi@getgrist.com');
|
||||
const indexOfChimpy = users.indexOf('chimpy@getgrist.com');
|
||||
const indexOfKiwi = users.indexOf('kiwi@getgrist.com');
|
||||
assert.isBelow(indexOfKiwi, indexOfChimpy, 'kiwi should come before chimpy');
|
||||
});
|
||||
|
||||
it('should filter the users by userName', async function () {
|
||||
const res = await axios.post(scimUrl('/Users/.search'), {
|
||||
schemas: [SEARCH_SCHEMA],
|
||||
attributes: ['userName'],
|
||||
filter: 'userName sw "chimpy"',
|
||||
}, chimpy);
|
||||
assert.equal(res.status, 200);
|
||||
assert.equal(res.data.totalResults, 1);
|
||||
assert.deepEqual(res.data.Resources[0], { id: String(userIdByName['chimpy']), userName: 'chimpy@getgrist.com' },
|
||||
"should have retrieved only chimpy's username and not other attribute");
|
||||
});
|
||||
|
||||
checkEndpointNotAccessibleForNonAdminUsers('post', '/Users/.search');
|
||||
});
|
||||
|
||||
describe('POST /Users', function () { // Create a new users
|
||||
it('should create a new user', async function () {
|
||||
const res = await axios.post(scimUrl('/Users'), toSCIMUserWithoutId('newuser1'), chimpy);
|
||||
assert.equal(res.status, 201);
|
||||
const newUserId = await getUserId('newuser1');
|
||||
assert.deepEqual(res.data, toSCIMUserWithId('newuser1', newUserId));
|
||||
});
|
||||
|
||||
it('should allow creating a new user given only their email passed as username', async function () {
|
||||
const res = await axios.post(scimUrl('/Users'), {
|
||||
schemas: ['urn:ietf:params:scim:schemas:core:2.0:User'],
|
||||
userName: 'new.user2@getgrist.com',
|
||||
}, chimpy);
|
||||
assert.equal(res.status, 201);
|
||||
assert.equal(res.data.userName, 'new.user2@getgrist.com');
|
||||
assert.equal(res.data.displayName, 'new.user2');
|
||||
});
|
||||
|
||||
it('should reject when passed email differs from username', async function () {
|
||||
const res = await axios.post(scimUrl('/Users'), {
|
||||
schemas: ['urn:ietf:params:scim:schemas:core:2.0:User'],
|
||||
userName: 'username@getgrist.com',
|
||||
emails: [{ value: 'emails.value@getgrist.com' }],
|
||||
}, chimpy);
|
||||
assert.equal(res.status, 400);
|
||||
assert.deepEqual(res.data, {
|
||||
schemas: [ 'urn:ietf:params:scim:api:messages:2.0:Error' ],
|
||||
status: '400',
|
||||
detail: 'Email and userName must be the same',
|
||||
scimType: 'invalidValue'
|
||||
});
|
||||
});
|
||||
|
||||
it('should disallow creating a user with the same email', async function () {
|
||||
const res = await axios.post(scimUrl('/Users'), toSCIMUserWithoutId('chimpy'), chimpy);
|
||||
assert.equal(res.status, 409);
|
||||
assert.deepEqual(res.data, {
|
||||
schemas: [ 'urn:ietf:params:scim:api:messages:2.0:Error' ],
|
||||
status: '409',
|
||||
detail: 'An existing user with the passed email exist.',
|
||||
scimType: 'uniqueness'
|
||||
});
|
||||
});
|
||||
|
||||
checkEndpointNotAccessibleForNonAdminUsers('post', '/Users');
|
||||
});
|
||||
|
||||
describe('PUT /Users/{id}', function () {
|
||||
let userToUpdateId: number;
|
||||
const userToUpdateEmailLocalPart = 'user-to-update';
|
||||
|
||||
beforeEach(async function () {
|
||||
userToUpdateId = await getUserId(userToUpdateEmailLocalPart);
|
||||
});
|
||||
afterEach(async function () {
|
||||
await server.dbManager.deleteUser({ userId: userToUpdateId }, userToUpdateId);
|
||||
});
|
||||
|
||||
it('should update an existing user', async function () {
|
||||
const userToUpdateProperties = {
|
||||
schemas: ['urn:ietf:params:scim:schemas:core:2.0:User'],
|
||||
userName: userToUpdateEmailLocalPart + '-now-updated@getgrist.com',
|
||||
displayName: 'User to Update',
|
||||
photos: [{ value: 'https://example.com/photo.jpg', type: 'photo', primary: true }],
|
||||
locale: 'fr',
|
||||
};
|
||||
const res = await axios.put(scimUrl(`/Users/${userToUpdateId}`), userToUpdateProperties, chimpy);
|
||||
assert.equal(res.status, 200);
|
||||
const refreshedUser = await axios.get(scimUrl(`/Users/${userToUpdateId}`), chimpy);
|
||||
assert.deepEqual(refreshedUser.data, {
|
||||
...userToUpdateProperties,
|
||||
id: String(userToUpdateId),
|
||||
meta: { resourceType: 'User', location: `/api/scim/v2/Users/${userToUpdateId}` },
|
||||
emails: [ { value: userToUpdateProperties.userName, primary: true } ],
|
||||
name: { formatted: userToUpdateProperties.displayName },
|
||||
preferredLanguage: 'fr',
|
||||
});
|
||||
});
|
||||
|
||||
it('should reject when passed email differs from username', async function () {
|
||||
const res = await axios.put(scimUrl(`/Users/${userToUpdateId}`), {
|
||||
schemas: ['urn:ietf:params:scim:schemas:core:2.0:User'],
|
||||
userName: userToUpdateEmailLocalPart + '@getgrist.com',
|
||||
emails: [{ value: 'whatever@getgrist.com', primary: true }],
|
||||
}, chimpy);
|
||||
assert.equal(res.status, 400);
|
||||
assert.deepEqual(res.data, {
|
||||
schemas: [ 'urn:ietf:params:scim:api:messages:2.0:Error' ],
|
||||
status: '400',
|
||||
detail: 'Email and userName must be the same',
|
||||
scimType: 'invalidValue'
|
||||
});
|
||||
});
|
||||
|
||||
it('should disallow updating a user with the same email', async function () {
|
||||
const res = await axios.put(scimUrl(`/Users/${userToUpdateId}`), toSCIMUserWithoutId('chimpy'), chimpy);
|
||||
assert.equal(res.status, 409);
|
||||
assert.deepEqual(res.data, {
|
||||
schemas: [ 'urn:ietf:params:scim:api:messages:2.0:Error' ],
|
||||
status: '409',
|
||||
detail: 'SQLITE_CONSTRAINT: UNIQUE constraint failed: logins.email',
|
||||
scimType: 'uniqueness'
|
||||
});
|
||||
});
|
||||
|
||||
it('should return 404 when the user is not found', async function () {
|
||||
const res = await axios.put(scimUrl('/Users/1000'), toSCIMUserWithoutId('chimpy'), chimpy);
|
||||
assert.equal(res.status, 404);
|
||||
assert.deepEqual(res.data, {
|
||||
schemas: [ 'urn:ietf:params:scim:api:messages:2.0:Error' ],
|
||||
status: '404',
|
||||
detail: 'unable to find user to update'
|
||||
});
|
||||
});
|
||||
|
||||
it('should deduce the name from the displayEmail when not provided', async function () {
|
||||
const res = await axios.put(scimUrl(`/Users/${userToUpdateId}`), {
|
||||
schemas: ['urn:ietf:params:scim:schemas:core:2.0:User'],
|
||||
userName: 'my-email@getgrist.com',
|
||||
}, chimpy);
|
||||
assert.equal(res.status, 200);
|
||||
assert.deepInclude(res.data, {
|
||||
schemas: ['urn:ietf:params:scim:schemas:core:2.0:User'],
|
||||
id: String(userToUpdateId),
|
||||
userName: 'my-email@getgrist.com',
|
||||
displayName: 'my-email',
|
||||
});
|
||||
});
|
||||
|
||||
it('should normalize the passed email for the userName and keep the case for email.value', async function () {
|
||||
const newEmail = 'my-EMAIL@getgrist.com';
|
||||
const res = await axios.put(scimUrl(`/Users/${userToUpdateId}`), {
|
||||
schemas: ['urn:ietf:params:scim:schemas:core:2.0:User'],
|
||||
userName: newEmail,
|
||||
}, chimpy);
|
||||
assert.equal(res.status, 200);
|
||||
assert.deepInclude(res.data, {
|
||||
schemas: ['urn:ietf:params:scim:schemas:core:2.0:User'],
|
||||
id: String(userToUpdateId),
|
||||
userName: newEmail.toLowerCase(),
|
||||
displayName: 'my-EMAIL',
|
||||
emails: [{ value: newEmail, primary: true }]
|
||||
});
|
||||
});
|
||||
|
||||
checkEndpointNotAccessibleForNonAdminUsers('put', '/Users/1');
|
||||
});
|
||||
|
||||
describe('PATCH /Users/{id}', function () {
|
||||
it('should update the user of id=1', async function () {
|
||||
throw new Error("This is a reminder :)");
|
||||
});
|
||||
checkEndpointNotAccessibleForNonAdminUsers('patch', '/Users/1');
|
||||
});
|
||||
|
||||
describe('DELETE /Users/{id}', function () {
|
||||
it('should delete the user of id=1', async function () {
|
||||
throw new Error("This is a reminder :)");
|
||||
});
|
||||
checkEndpointNotAccessibleForNonAdminUsers('delete', '/Users/1');
|
||||
});
|
||||
|
||||
it('should fix the 401 response for authenticated users', function () {
|
||||
throw new Error("This is a reminder :)");
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue
Block a user