updated tap (#1615)

* updated tap

* Fix the tests

* Update package-lock.json

* upgrade to 18.1.1

* Update package-lock.json

* Update package.json

---------

Co-authored-by: Kiran K <mailtokirankk@gmail.com>
Co-authored-by: Kiran K <kiran@boxyhq.com>
This commit is contained in:
Deepak Prabhakara 2023-09-25 11:03:21 +01:00 committed by GitHub
parent c74ff3be80
commit 89d44ca903
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 21400 additions and 6993 deletions

1
.gitignore vendored
View File

@ -45,3 +45,4 @@ publishTag.txt
.env
_dev/docker/dynamodb/shared-local-instance.db
public/terminus/sprites.png
**/.tap/**

View File

@ -46,5 +46,5 @@ const map = {
};
module.exports = (testFile) => {
return map[testFile];
return map[testFile] || [];
};

6457
npm/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -29,15 +29,11 @@
"db:migration:run:mariadb": "cross-env DB_TYPE=mariadb DB_URL=mariadb://root@localhost:3306/mysql ts-node --transpile-only ./node_modules/typeorm/cli.js migration:run -d typeorm.ts",
"db:migration:run:mssql": "cross-env DB_TYPE=mssql DB_URL='sqlserver://localhost:1433;database=master;username=sa;password=123ABabc!' ts-node --transpile-only ./node_modules/typeorm/cli.js migration:run -d typeorm.ts",
"prepublishOnly": "npm run build",
"test": "cross-env BOXYHQ_NO_ANALYTICS=1 tap -T --ts --coverage-map=map.js test/**/*.test.ts",
"test": "cross-env BOXYHQ_NO_ANALYTICS=1 tap --timeout=0 --allow-incomplete-coverage --allow-empty-coverage test/**/*.test.ts",
"sort": "npx sort-package-json"
},
"tap": {
"branches": 50,
"coverage-map": "map.js",
"functions": 70,
"lines": 70,
"statements": 70
"coverage-map": "map.js"
},
"dependencies": {
"@aws-sdk/client-dynamodb": "3.417.0",
@ -74,7 +70,7 @@
"cross-env": "7.0.3",
"nock": "13.3.3",
"sinon": "16.0.0",
"tap": "16.3.8",
"tap": "18.1.1",
"ts-node": "10.9.1",
"tsconfig-paths": "4.2.0",
"typescript": "5.2.2"

View File

@ -25,105 +25,108 @@ tap.before(async () => {
});
tap.teardown(async () => {
// Delete the directory after test
await directorySync.directories.delete(directory.id);
process.exit(0);
});
tap.test('Directory groups / ', async (t) => {
let createdGroup: any;
tap.beforeEach(async () => {
// Create a group before each test
const { data } = await directorySync.requests.handle(groupsRequest.create(directory, groups[0]));
createdGroup = data;
tap.test('Directory groups /', async (t) => {
t.teardown(async () => {
await directorySync.directories.delete(directory.id);
});
tap.afterEach(async () => {
// Delete the group after each test
await directorySync.groups.delete(createdGroup.id);
});
t.test('Directory groups /', async (t) => {
let createdGroup: any;
t.test('Should be able to create a new group', async (t) => {
t.ok(createdGroup);
t.hasStrict(createdGroup, groups[0]);
t.ok('id' in createdGroup);
});
t.beforeEach(async () => {
// Create a group before each test
const { data } = await directorySync.requests.handle(groupsRequest.create(directory, groups[0]));
t.test('Should be able to get the group by id', async (t) => {
const request = groupsRequest.getById(directory, createdGroup.id);
const { status, data } = await directorySync.requests.handle(request);
t.ok(data);
t.equal(status, 200);
t.hasStrict(data, createdGroup);
t.hasStrict(data, groups[0]);
});
t.test('Should be able to get the group by displayName', async (t) => {
const request = groupsRequest.filterByDisplayName(directory, createdGroup.displayName);
const { status, data } = await directorySync.requests.handle(request);
t.ok(data);
t.equal(status, 200);
t.hasStrict(data.Resources[0], createdGroup);
t.hasStrict(data.Resources[0], groups[0]);
t.equal(data.Resources.length, 1);
});
t.test('Should be able to get all groups', async (t) => {
const request = groupsRequest.getAll(directory);
const { status, data } = await directorySync.requests.handle(request);
t.ok(data);
t.equal(status, 200);
t.hasStrict(data.Resources[0], createdGroup);
t.hasStrict(data.Resources[0], groups[0]);
t.equal(data.totalResults, 1);
t.equal(data.Resources[0].members.length, 0);
});
t.test('Should be able to update the group name', async (t) => {
const request = groupsRequest.updateName(directory, createdGroup.id, {
...createdGroup,
displayName: 'Developers Updated',
createdGroup = data;
});
const callback = async (event: DirectorySyncEvent) => {
t.match(event.event, 'group.updated');
t.match(event.data, { id: createdGroup.id, name: 'Developers Updated' });
};
t.afterEach(async () => {
// Delete the group after each test
await directorySync.groups.delete(createdGroup.id);
});
const { status, data } = await directorySync.requests.handle(request, callback);
t.test('Should be able to create a new group', async (t) => {
t.ok(createdGroup);
t.hasStrict(createdGroup, groups[0]);
t.ok('id' in createdGroup);
});
t.ok(data);
t.equal(status, 200);
t.equal(data.displayName, 'Developers Updated');
});
t.test('Should be able to get the group by id', async (t) => {
const request = groupsRequest.getById(directory, createdGroup.id);
t.test('Should be able to delete a group', async (t) => {
const request = groupsRequest.deleteById(directory, createdGroup.id);
const { status, data } = await directorySync.requests.handle(request);
const callback = async (event: DirectorySyncEvent) => {
t.match(event.event, 'group.deleted');
t.match(event.data, { id: createdGroup.id, name: 'Developers' });
};
t.ok(data);
t.equal(status, 200);
t.hasStrict(data, createdGroup);
t.hasStrict(data, groups[0]);
});
const { status } = await directorySync.requests.handle(request, callback);
t.test('Should be able to get the group by displayName', async (t) => {
const request = groupsRequest.filterByDisplayName(directory, createdGroup.displayName);
t.equal(status, 200);
const { status, data } = await directorySync.requests.handle(request);
// Try to get the group
try {
await directorySync.requests.handle(groupsRequest.getById(directory, createdGroup.id));
} catch (e: any) {
t.equal(e.statusCode, 404);
t.equal(e.message, `Group with id ${createdGroup.id} not found.`);
}
t.ok(data);
t.equal(status, 200);
t.hasStrict(data.Resources[0], createdGroup);
t.hasStrict(data.Resources[0], groups[0]);
t.equal(data.Resources.length, 1);
});
t.test('Should be able to get all groups', async (t) => {
const request = groupsRequest.getAll(directory);
const { status, data } = await directorySync.requests.handle(request);
t.ok(data);
t.equal(status, 200);
t.hasStrict(data.Resources[0], createdGroup);
t.hasStrict(data.Resources[0], groups[0]);
t.equal(data.totalResults, 1);
t.equal(data.Resources[0].members.length, 0);
});
t.test('Should be able to update the group name', async (t) => {
const request = groupsRequest.updateName(directory, createdGroup.id, {
...createdGroup,
displayName: 'Developers Updated',
});
const callback = async (event: DirectorySyncEvent) => {
t.match(event.event, 'group.updated');
t.match(event.data, { id: createdGroup.id, name: 'Developers Updated' });
};
const { status, data } = await directorySync.requests.handle(request, callback);
t.ok(data);
t.equal(status, 200);
t.equal(data.displayName, 'Developers Updated');
});
t.test('Should be able to delete a group', async (t) => {
const request = groupsRequest.deleteById(directory, createdGroup.id);
const callback = async (event: DirectorySyncEvent) => {
t.match(event.event, 'group.deleted');
t.match(event.data, { id: createdGroup.id, name: 'Developers' });
};
const { status } = await directorySync.requests.handle(request, callback);
t.equal(status, 200);
// Try to get the group
try {
await directorySync.requests.handle(groupsRequest.getById(directory, createdGroup.id));
} catch (e: any) {
t.equal(e.statusCode, 404);
t.equal(e.message, `Group with id ${createdGroup.id} not found.`);
}
});
});
});

View File

@ -43,118 +43,122 @@ tap.before(async () => {
});
tap.teardown(async () => {
await directorySync.directories.delete(directory.id);
await directorySync.groups.delete(group.id);
process.exit(0);
});
tap.test('Directory groups membership / ', async (t) => {
const { data: user1 } = await directorySync.requests.handle(usersRequest.create(directory, users[0]));
const { data: user2 } = await directorySync.requests.handle(usersRequest.create(directory, users[1]));
t.match(await directorySync.groups.isUserInGroup(group.id, user1.id), false);
let request = createGroupMembershipRequest(directory, group, [
{
op: 'add',
path: 'members',
value: [
{
value: user1.id,
},
],
},
]);
// Add a member to an existing group
await directorySync.requests.handle(request, async (event: DirectorySyncEvent) => {
t.match(event.event, 'group.user_added');
t.match(event.data.id, user1.id);
if ('group' in event.data) {
t.match(event.data.group.id, group.id);
t.match(event.data.group.name, 'Developers');
}
tap.test('Directory groups membership /', async (t) => {
t.teardown(async () => {
await directorySync.directories.delete(directory.id);
await directorySync.groups.delete(group.id);
});
t.match(await directorySync.groups.isUserInGroup(group.id, user1.id), true);
t.test('Directory groups membership /', async (t) => {
const { data: user1 } = await directorySync.requests.handle(usersRequest.create(directory, users[0]));
const { data: user2 } = await directorySync.requests.handle(usersRequest.create(directory, users[1]));
request = createGroupMembershipRequest(directory, group, [
{
op: 'remove',
path: `members[value eq "${user1.id}"]`,
},
]);
t.match(await directorySync.groups.isUserInGroup(group.id, user1.id), false);
// Remove a member from an existing group
await directorySync.requests.handle(request, async (event: DirectorySyncEvent) => {
t.match(event.event, 'group.user_removed');
t.match(event.data.id, user1.id);
let request = createGroupMembershipRequest(directory, group, [
{
op: 'add',
path: 'members',
value: [
{
value: user1.id,
},
],
},
]);
if ('group' in event.data) {
t.match(event.data.group.id, group.id);
t.match(event.data.group.name, 'Developers');
}
// Add a member to an existing group
await directorySync.requests.handle(request, async (event: DirectorySyncEvent) => {
t.match(event.event, 'group.user_added');
t.match(event.data.id, user1.id);
if ('group' in event.data) {
t.match(event.data.group.id, group.id);
t.match(event.data.group.name, 'Developers');
}
});
t.match(await directorySync.groups.isUserInGroup(group.id, user1.id), true);
request = createGroupMembershipRequest(directory, group, [
{
op: 'remove',
path: `members[value eq "${user1.id}"]`,
},
]);
// Remove a member from an existing group
await directorySync.requests.handle(request, async (event: DirectorySyncEvent) => {
t.match(event.event, 'group.user_removed');
t.match(event.data.id, user1.id);
if ('group' in event.data) {
t.match(event.data.group.id, group.id);
t.match(event.data.group.name, 'Developers');
}
});
t.match(await directorySync.groups.isUserInGroup(group.id, user1.id), false);
request = createGroupMembershipRequest(directory, group, [
{
op: 'add',
path: 'members',
value: [
{
value: user1.id,
},
{
value: user2.id,
},
],
},
]);
// Handle multiple operations in a single request
await directorySync.requests.handle(request, async (event: DirectorySyncEvent) => {
t.match(event.event, 'group.user_added');
t.match([user1.id, user2.id].includes(event.data.id), true);
if ('group' in event.data) {
t.match(event.data.group.id, group.id);
t.match(event.data.group.name, 'Developers');
}
});
t.match(await directorySync.groups.isUserInGroup(group.id, user1.id), true);
t.match(await directorySync.groups.isUserInGroup(group.id, user2.id), true);
request = createGroupMembershipRequest(directory, group, [
{
op: 'remove',
path: 'members',
value: [
{
value: user1.id,
},
{
value: user2.id,
},
],
},
]);
// Remove all members from an existing group
await directorySync.requests.handle(request, async (event: DirectorySyncEvent) => {
t.match(event.event, 'group.user_removed');
t.match([user1.id, user2.id].includes(event.data.id), true);
if ('group' in event.data) {
t.match(event.data.group.id, group.id);
t.match(event.data.group.name, 'Developers');
}
});
t.match(await directorySync.groups.isUserInGroup(group.id, user1.id), false);
t.match(await directorySync.groups.isUserInGroup(group.id, user2.id), false);
});
t.match(await directorySync.groups.isUserInGroup(group.id, user1.id), false);
request = createGroupMembershipRequest(directory, group, [
{
op: 'add',
path: 'members',
value: [
{
value: user1.id,
},
{
value: user2.id,
},
],
},
]);
// Handle multiple operations in a single request
await directorySync.requests.handle(request, async (event: DirectorySyncEvent) => {
t.match(event.event, 'group.user_added');
t.match([user1.id, user2.id].includes(event.data.id), true);
if ('group' in event.data) {
t.match(event.data.group.id, group.id);
t.match(event.data.group.name, 'Developers');
}
});
t.match(await directorySync.groups.isUserInGroup(group.id, user1.id), true);
t.match(await directorySync.groups.isUserInGroup(group.id, user2.id), true);
request = createGroupMembershipRequest(directory, group, [
{
op: 'remove',
path: 'members',
value: [
{
value: user1.id,
},
{
value: user2.id,
},
],
},
]);
// Remove all members from an existing group
await directorySync.requests.handle(request, async (event: DirectorySyncEvent) => {
t.match(event.event, 'group.user_removed');
t.match([user1.id, user2.id].includes(event.data.id), true);
if ('group' in event.data) {
t.match(event.data.group.id, group.id);
t.match(event.data.group.name, 'Developers');
}
});
t.match(await directorySync.groups.isUserInGroup(group.id, user1.id), false);
t.match(await directorySync.groups.isUserInGroup(group.id, user2.id), false);
});

View File

@ -25,148 +25,153 @@ tap.before(async () => {
});
tap.teardown(async () => {
// Delete the directory after test
await directorySync.directories.delete(directory.id);
process.exit(0);
});
tap.test('Directory users / ', async (t) => {
let createdUser: any;
tap.beforeEach(async () => {
// Create a user before each test
const { data } = await directorySync.requests.handle(requests.create(directory, users[0]));
createdUser = data;
tap.test('Directory users /', async (t) => {
t.teardown(async () => {
await directorySync.directories.delete(directory.id);
});
tap.afterEach(async () => {
// Delete the user after each test
await directorySync.users.delete(createdUser.id);
});
t.test('Directory users /', async (t) => {
let createdUser: any;
t.test('Should be able to get the user by userName', async (t) => {
const { status, data } = await directorySync.requests.handle(
requests.filterByUsername(directory, createdUser.userName)
);
t.beforeEach(async () => {
// Create a user before each test
const { data } = await directorySync.requests.handle(requests.create(directory, users[0]));
t.ok(data);
t.equal(status, 200);
t.hasStrict(data.Resources[0], createdUser);
t.hasStrict(data.Resources[0], users[0]);
});
createdUser = data;
});
t.test('Should be able to get the user by id', async (t) => {
const { status, data } = await directorySync.requests.handle(requests.getById(directory, createdUser.id));
t.afterEach(async () => {
// Delete the user after each test
await directorySync.users.delete(createdUser.id);
});
t.ok(data);
t.equal(status, 200);
t.hasStrict(data, users[0]);
});
t.test('Should be able to get the user by userName', async (t) => {
const { status, data } = await directorySync.requests.handle(
requests.filterByUsername(directory, createdUser.userName)
);
t.test('Should be able to update the user using PUT request', async (t) => {
const toUpdate = {
...users[0],
name: {
givenName: 'Jackson Updated',
familyName: 'M',
},
city: 'New York',
};
t.ok(data);
t.equal(status, 200);
t.hasStrict(data.Resources[0], createdUser);
t.hasStrict(data.Resources[0], users[0]);
});
const { status, data: updatedUser } = await directorySync.requests.handle(
requests.updateById(directory, createdUser.id, toUpdate)
);
t.test('Should be able to get the user by id', async (t) => {
const { status, data } = await directorySync.requests.handle(
requests.getById(directory, createdUser.id)
);
t.ok(updatedUser);
t.equal(status, 200);
t.hasStrict(updatedUser, toUpdate);
t.match(updatedUser.city, toUpdate.city);
t.ok(data);
t.equal(status, 200);
t.hasStrict(data, users[0]);
});
// Make sure the user was updated
const { data: user } = await directorySync.requests.handle(requests.getById(directory, createdUser.id));
t.test('Should be able to update the user using PUT request', async (t) => {
const toUpdate = {
...users[0],
name: {
givenName: 'Jackson Updated',
familyName: 'M',
},
city: 'New York',
};
t.ok(user);
t.hasStrict(user, toUpdate);
t.match(user.city, toUpdate.city);
});
const { status, data: updatedUser } = await directorySync.requests.handle(
requests.updateById(directory, createdUser.id, toUpdate)
);
t.test('Should be able to delete the user using PATCH request', async (t) => {
const toUpdate = {
...users[0],
active: false,
};
t.ok(updatedUser);
t.equal(status, 200);
t.hasStrict(updatedUser, toUpdate);
t.match(updatedUser.city, toUpdate.city);
const { status, data } = await directorySync.requests.handle(
requests.updateOperationById(directory, createdUser.id)
);
// Make sure the user was updated
const { data: user } = await directorySync.requests.handle(requests.getById(directory, createdUser.id));
t.ok(data);
t.equal(status, 200);
t.hasStrict(data, toUpdate);
});
t.ok(user);
t.hasStrict(user, toUpdate);
t.match(user.city, toUpdate.city);
});
t.test('should be able to update the user with multi-valued properties', async (t) => {
const { status, data } = await directorySync.requests.handle(
requests.multiValuedProperties(directory, createdUser.id)
);
t.test('Should be able to delete the user using PATCH request', async (t) => {
const toUpdate = {
...users[0],
active: false,
};
t.ok(data);
t.equal(status, 200);
t.equal(data.active, false);
t.equal(data.name.givenName, 'David');
t.equal(data.name.familyName, 'Jones');
});
const { status, data } = await directorySync.requests.handle(
requests.updateOperationById(directory, createdUser.id)
);
t.test('Should be able to update the custom user attributes', async (t) => {
const { status, data } = await directorySync.requests.handle(
requests.customAttributes(directory, createdUser.id)
);
t.ok(data);
t.equal(status, 200);
t.hasStrict(data, toUpdate);
});
t.ok(data);
t.equal(status, 200);
t.equal(data.companyName, 'BoxyHQ');
t.equal(data.address.streetAddress, '123 Main St');
});
t.test('should be able to update the user with multi-valued properties', async (t) => {
const { status, data } = await directorySync.requests.handle(
requests.multiValuedProperties(directory, createdUser.id)
);
t.test('Should be able to fetch all users', async (t) => {
const { status, data } = await directorySync.requests.handle(requests.getAll(directory));
t.ok(data);
t.equal(status, 200);
t.equal(data.active, false);
t.equal(data.name.givenName, 'David');
t.equal(data.name.familyName, 'Jones');
});
t.ok(data);
t.equal(status, 200);
t.ok(data.Resources);
t.equal(data.Resources.length, 1);
t.hasStrict(data.Resources[0], users[0]);
t.equal(data.totalResults, 1);
});
t.test('Should be able to update the custom user attributes', async (t) => {
const { status, data } = await directorySync.requests.handle(
requests.customAttributes(directory, createdUser.id)
);
t.test('Should be able to delete the user', async (t) => {
const { status, data } = await directorySync.requests.handle(
requests.deleteById(directory, createdUser.id)
);
t.ok(data);
t.equal(status, 200);
t.equal(data.companyName, 'BoxyHQ');
t.equal(data.address.streetAddress, '123 Main St');
});
t.equal(status, 200);
t.ok(data);
t.strictSame(data, createdUser);
t.test('Should be able to fetch all users', async (t) => {
const { status, data } = await directorySync.requests.handle(requests.getAll(directory));
// Make sure the user was deleted
const { data: user } = await directorySync.requests.handle(
requests.filterByUsername(directory, createdUser.userName)
);
t.ok(data);
t.equal(status, 200);
t.ok(data.Resources);
t.equal(data.Resources.length, 1);
t.hasStrict(data.Resources[0], users[0]);
t.equal(data.totalResults, 1);
});
t.hasStrict(user.Resources, []);
t.hasStrict(user.totalResults, 0);
});
t.test('Should be able to delete the user', async (t) => {
const { status, data } = await directorySync.requests.handle(
requests.deleteById(directory, createdUser.id)
);
t.test('Should be able to delete all users using deleteAll() method', async (t) => {
directorySync.users.setTenantAndProduct(directory.tenant, directory.product);
t.equal(status, 200);
t.ok(data);
t.strictSame(data, createdUser);
await directorySync.users.deleteAll(directory.id);
// Make sure the user was deleted
const { data: user } = await directorySync.requests.handle(
requests.filterByUsername(directory, createdUser.userName)
);
// Make sure all the user was deleted
const { data: users } = await directorySync.users.getAll();
t.hasStrict(user.Resources, []);
t.hasStrict(user.totalResults, 0);
});
t.equal(users?.length, 0);
t.test('Should be able to delete all users using deleteAll() method', async (t) => {
directorySync.users.setTenantAndProduct(directory.tenant, directory.product);
await directorySync.users.deleteAll(directory.id);
// Make sure all the user was deleted
const { data: users } = await directorySync.users.getAll();
t.equal(users?.length, 0);
});
});
});

View File

@ -52,246 +52,249 @@ tap.before(async () => {
});
tap.teardown(async () => {
// Delete the directory after the test
await directorySync.directories.delete(directory.id);
process.exit(0);
});
tap.test('Webhook Events / ', async (t) => {
tap.afterEach(async () => {
await directorySync.webhookLogs.deleteAll(directory.id);
tap.test('Webhook Events /', async (t) => {
t.teardown(async () => {
await directorySync.directories.delete(directory.id);
});
t.test("Should be able to get the directory's webhook", async (t) => {
t.match(directory.webhook.endpoint, webhook.endpoint);
t.match(directory.webhook.secret, webhook.secret);
});
t.test('Should not log events if the directory has no webhook', async (t) => {
await directorySync.directories.update(directory.id, {
webhook: {
endpoint: '',
secret: '',
},
t.test('Webhook Events / ', async (t) => {
t.afterEach(async () => {
await directorySync.webhookLogs.deleteAll(directory.id);
});
// Create a user
await directorySync.requests.handle(usersRequest.create(directory, users[0]), eventCallback);
const events = await directorySync.webhookLogs.getAll();
t.equal(events.length, 0);
// Restore the directory's webhook
await directorySync.directories.update(directory.id, {
webhook: {
endpoint: webhook.endpoint,
secret: webhook.secret,
},
});
});
t.test('Should not log webhook events if the logging is turned off', async (t) => {
// Turn off webhook event logging for the directory
await directorySync.directories.update(directory.id, {
log_webhook_events: false,
t.test("Should be able to get the directory's webhook", async (t) => {
t.match(directory.webhook.endpoint, webhook.endpoint);
t.match(directory.webhook.secret, webhook.secret);
});
// Create a user
await directorySync.requests.handle(usersRequest.create(directory, users[0]), eventCallback);
t.test('Should not log events if the directory has no webhook', async (t) => {
await directorySync.directories.update(directory.id, {
webhook: {
endpoint: '',
secret: '',
},
});
const events = await directorySync.webhookLogs.getAll();
// Create a user
await directorySync.requests.handle(usersRequest.create(directory, users[0]), eventCallback);
t.equal(events.length, 0);
const events = await directorySync.webhookLogs.getAll();
// Turn on webhook event logging for the directory
await directorySync.directories.update(directory.id, {
log_webhook_events: true,
t.equal(events.length, 0);
// Restore the directory's webhook
await directorySync.directories.update(directory.id, {
webhook: {
endpoint: webhook.endpoint,
secret: webhook.secret,
},
});
});
});
t.test('Should be able to get an event by id', async (t) => {
// Create a user
await directorySync.requests.handle(usersRequest.create(directory, users[0]), eventCallback);
t.test('Should not log webhook events if the logging is turned off', async (t) => {
// Turn off webhook event logging for the directory
await directorySync.directories.update(directory.id, {
log_webhook_events: false,
});
const logs = await directorySync.webhookLogs.getAll();
// Create a user
await directorySync.requests.handle(usersRequest.create(directory, users[0]), eventCallback);
const log = await directorySync.webhookLogs.get(logs[0].id);
const events = await directorySync.webhookLogs.getAll();
t.equal(log.id, logs[0].id);
});
t.equal(events.length, 0);
t.test('Should send user related events', async (t) => {
const mock = sinon.mock(axios);
// Turn on webhook event logging for the directory
await directorySync.directories.update(directory.id, {
log_webhook_events: true,
});
});
mock.expects('post').thrice().withArgs(webhook.endpoint).throws();
t.test('Should be able to get an event by id', async (t) => {
// Create a user
await directorySync.requests.handle(usersRequest.create(directory, users[0]), eventCallback);
// Create the user
const { data: createdUser } = await directorySync.requests.handle(
usersRequest.create(directory, users[0]),
eventCallback
);
const logs = await directorySync.webhookLogs.getAll();
// Update the user
const { data: updatedUser } = await directorySync.requests.handle(
usersRequest.updateById(directory, createdUser.id, users[0]),
eventCallback
);
const log = await directorySync.webhookLogs.get(logs[0].id);
// Delete the user
const { data: deletedUser } = await directorySync.requests.handle(
usersRequest.deleteById(directory, createdUser.id),
eventCallback
);
t.equal(log.id, logs[0].id);
});
mock.verify();
mock.restore();
t.test('Should send user related events', async (t) => {
const mock = sinon.mock(axios);
const logs = await directorySync.webhookLogs.getAll();
mock.expects('post').thrice().withArgs(webhook.endpoint).throws();
t.ok(logs);
t.equal(logs.length, 3);
// Create the user
const { data: createdUser } = await directorySync.requests.handle(
usersRequest.create(directory, users[0]),
eventCallback
);
t.match(logs[0].event, 'user.deleted');
t.match(logs[0].directory_id, directory.id);
t.hasStrict(logs[0].data.raw, deletedUser);
// Update the user
const { data: updatedUser } = await directorySync.requests.handle(
usersRequest.updateById(directory, createdUser.id, users[0]),
eventCallback
);
t.match(logs[1].event, 'user.updated');
t.match(logs[1].directory_id, directory.id);
t.hasStrict(logs[1].data.raw, updatedUser);
// Delete the user
const { data: deletedUser } = await directorySync.requests.handle(
usersRequest.deleteById(directory, createdUser.id),
eventCallback
);
t.match(logs[2].event, 'user.created');
t.match(logs[2].directory_id, directory.id);
t.hasStrict(logs[2].data.raw, createdUser);
mock.verify();
mock.restore();
await directorySync.users.deleteAll(directory.id);
});
const logs = await directorySync.webhookLogs.getAll();
t.test('Should send group related events', async (t) => {
const mock = sinon.mock(axios);
t.ok(logs);
t.equal(logs.length, 3);
mock.expects('post').thrice().withArgs(webhook.endpoint).throws();
t.match(logs[0].event, 'user.deleted');
t.match(logs[0].directory_id, directory.id);
t.hasStrict(logs[0].data.raw, deletedUser);
// Create the group
const { data: createdGroup } = await directorySync.requests.handle(
groupRequest.create(directory, groups[0]),
eventCallback
);
t.match(logs[1].event, 'user.updated');
t.match(logs[1].directory_id, directory.id);
t.hasStrict(logs[1].data.raw, updatedUser);
// Update the group
const { data: updatedGroup } = await directorySync.requests.handle(
groupRequest.updateById(directory, createdGroup.id, groups[0]),
eventCallback
);
t.match(logs[2].event, 'user.created');
t.match(logs[2].directory_id, directory.id);
t.hasStrict(logs[2].data.raw, createdUser);
// Delete the group
const { data: deletedGroup } = await directorySync.requests.handle(
groupRequest.deleteById(directory, createdGroup.id),
eventCallback
);
await directorySync.users.deleteAll(directory.id);
});
mock.verify();
mock.restore();
t.test('Should send group related events', async (t) => {
const mock = sinon.mock(axios);
const logs = await directorySync.webhookLogs.getAll();
mock.expects('post').thrice().withArgs(webhook.endpoint).throws();
t.ok(logs);
t.equal(logs.length, 3);
// Create the group
const { data: createdGroup } = await directorySync.requests.handle(
groupRequest.create(directory, groups[0]),
eventCallback
);
t.match(logs[0].event, 'group.deleted');
t.match(logs[0].directory_id, directory.id);
t.hasStrict(logs[0].data.raw, deletedGroup);
// Update the group
const { data: updatedGroup } = await directorySync.requests.handle(
groupRequest.updateById(directory, createdGroup.id, groups[0]),
eventCallback
);
t.match(logs[1].event, 'group.updated');
t.match(logs[1].directory_id, directory.id);
t.hasStrict(logs[1].data.raw, updatedGroup);
// Delete the group
const { data: deletedGroup } = await directorySync.requests.handle(
groupRequest.deleteById(directory, createdGroup.id),
eventCallback
);
t.match(logs[2].event, 'group.created');
t.match(logs[2].directory_id, directory.id);
t.hasStrict(logs[2].data.raw, createdGroup);
});
mock.verify();
mock.restore();
t.test('Should send group membership related events', async (t) => {
const mock = sinon.mock(axios);
const logs = await directorySync.webhookLogs.getAll();
mock.expects('post').exactly(4).withArgs(webhook.endpoint).throws();
t.ok(logs);
t.equal(logs.length, 3);
// Create the user
const { data: createdUser } = await directorySync.requests.handle(
usersRequest.create(directory, users[0]),
eventCallback
);
t.match(logs[0].event, 'group.deleted');
t.match(logs[0].directory_id, directory.id);
t.hasStrict(logs[0].data.raw, deletedGroup);
// Create the group
const { data: createdGroup } = await directorySync.requests.handle(
groupRequest.create(directory, groups[0]),
eventCallback
);
t.match(logs[1].event, 'group.updated');
t.match(logs[1].directory_id, directory.id);
t.hasStrict(logs[1].data.raw, updatedGroup);
// Add the user to the group
await directorySync.requests.handle(
groupRequest.addMembers(directory, createdGroup.id, [{ value: createdUser.id }]),
eventCallback
);
t.match(logs[2].event, 'group.created');
t.match(logs[2].directory_id, directory.id);
t.hasStrict(logs[2].data.raw, createdGroup);
});
// Remove the user from the group
await directorySync.requests.handle(
groupRequest.removeMembers(
directory,
createdGroup.id,
[{ value: createdUser.id }],
`members[value eq "${createdUser.id}"]`
),
eventCallback
);
t.test('Should send group membership related events', async (t) => {
const mock = sinon.mock(axios);
mock.verify();
mock.restore();
mock.expects('post').exactly(4).withArgs(webhook.endpoint).throws();
const logs = await directorySync.webhookLogs.getAll();
// Create the user
const { data: createdUser } = await directorySync.requests.handle(
usersRequest.create(directory, users[0]),
eventCallback
);
t.ok(logs);
t.equal(logs.length, 4);
// Create the group
const { data: createdGroup } = await directorySync.requests.handle(
groupRequest.create(directory, groups[0]),
eventCallback
);
t.match(logs[0].event, 'group.user_removed');
t.match(logs[0].directory_id, directory.id);
t.hasStrict(logs[0].data.raw, createdUser);
// Add the user to the group
await directorySync.requests.handle(
groupRequest.addMembers(directory, createdGroup.id, [{ value: createdUser.id }]),
eventCallback
);
t.match(logs[1].event, 'group.user_added');
t.match(logs[1].directory_id, directory.id);
t.hasStrict(logs[1].data.raw, createdUser);
// Remove the user from the group
await directorySync.requests.handle(
groupRequest.removeMembers(
directory,
createdGroup.id,
[{ value: createdUser.id }],
`members[value eq "${createdUser.id}"]`
),
eventCallback
);
await directorySync.users.delete(createdUser.id);
await directorySync.groups.delete(createdGroup.id);
});
mock.verify();
mock.restore();
t.test('createSignatureString()', async (t) => {
const event: DirectorySyncEvent = {
event: 'user.created',
directory_id: directory.id,
tenant: directory.tenant,
product: directory.product,
data: {
raw: [],
id: 'user-id',
first_name: 'Kiran',
last_name: 'Krishnan',
email: 'kiran@boxyhq.com',
active: true,
},
};
const logs = await directorySync.webhookLogs.getAll();
const signatureString = createSignatureString(directory.webhook.secret, event);
const parts = signatureString.split(',');
t.ok(logs);
t.equal(logs.length, 4);
t.ok(signatureString);
t.ok(parts[0].match(/^t=[0-9a-f]/));
t.ok(parts[1].match(/^s=[0-9a-f]/));
t.match(logs[0].event, 'group.user_removed');
t.match(logs[0].directory_id, directory.id);
t.hasStrict(logs[0].data.raw, createdUser);
// Empty secret should create an empty signature
const emptySignatureString = createSignatureString('', event);
t.match(logs[1].event, 'group.user_added');
t.match(logs[1].directory_id, directory.id);
t.hasStrict(logs[1].data.raw, createdUser);
t.match(emptySignatureString, '');
await directorySync.users.delete(createdUser.id);
await directorySync.groups.delete(createdGroup.id);
});
t.test('createSignatureString()', async (t) => {
const event: DirectorySyncEvent = {
event: 'user.created',
directory_id: directory.id,
tenant: directory.tenant,
product: directory.product,
data: {
raw: [],
id: 'user-id',
first_name: 'Kiran',
last_name: 'Krishnan',
email: 'kiran@boxyhq.com',
active: true,
},
};
const signatureString = createSignatureString(directory.webhook.secret, event);
const parts = signatureString.split(',');
t.ok(signatureString);
t.ok(parts[0].match(/^t=[0-9a-f]/));
t.ok(parts[1].match(/^s=[0-9a-f]/));
// Empty secret should create an empty signature
const emptySignatureString = createSignatureString('', event);
t.match(emptySignatureString, '');
});
});
});

View File

@ -51,72 +51,79 @@ tap.before(async () => {
});
});
tap.test('Federated SAML flow', async () => {
const relayStateFromSP = 'sp-saml-request-relay-state';
const requestXML = await fs.readFile(path.join(__dirname, '/data/request.xml'), 'utf8');
const responseXML = await fs.readFile(path.join(__dirname, '/data/response.xml'), 'utf8');
const samlRequestFromSP = Buffer.from(await deflateRawAsync(requestXML)).toString('base64');
const samlResponseFromIdP = Buffer.from(responseXML).toString('base64');
let jacksonRelayState: string | null = null;
tap.test('Should be able to accept SAML Request from SP and generate SAML Request for IdP', async (t) => {
const response = await samlFederatedController.sso.getAuthorizeUrl({
request: samlRequestFromSP,
relayState: relayStateFromSP,
});
// Extract relay state created by Jackson
jacksonRelayState = new URL(response.redirectUrl).searchParams.get('RelayState');
t.ok(
response.redirectUrl?.startsWith(`${connection.idpMetadata.sso.redirectUrl}`),
'Should have a SSO URL that starts with IdP SSO URL'
);
t.ok(response.redirectUrl, 'Should have a redirect URL');
t.ok(response.redirectUrl?.includes('SAMLRequest'), 'Should have a SAMLRequest in the redirect URL');
t.ok(response.redirectUrl?.includes('RelayState'), 'Should have a RelayState in the redirect URL');
});
tap.test('Should be able to accept SAML Response from IdP and generate SAML Response for SP', async (t) => {
const stubValidate = sinon.stub(saml, 'validate').resolves({
audience: 'https://saml.boxyhq.com',
claims: {
id: '00u3e3cmpdDydXdzV5d7',
email: 'kiran@boxyhq.com',
firstName: 'Kiran',
lastName: 'Krishnan',
'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier': 'kiran@boxyhq.com',
},
issuer: 'https://saml.example.com/entityid',
sessionIndex: '_a30730c45288bbc4986b',
});
const response = await oauthController.samlResponse({
SAMLResponse: samlResponseFromIdP,
RelayState: jacksonRelayState ?? '',
});
t.ok(response);
t.ok('responseForm' in response);
t.ok(response.responseForm?.includes('SAMLResponse'), 'Should have a SAMLResponse in the response form');
t.ok(response.responseForm?.includes('RelayState'), 'Should have a RelayState in the response form');
const relayState = response.responseForm
? response.responseForm.match(/<input type="hidden" name="RelayState" value="(.*)"\/>/)?.[1]
: null;
t.match(relayState, relayStateFromSP, 'Should have the same relay state as the one sent by SP');
stubValidate.restore();
});
});
tap.teardown(async () => {
await samlFederatedController.app.delete(app.id);
await connectionAPIController.deleteConnections({ tenant, product });
process.exit(0);
});
tap.test('Federated SAML flow', async (t) => {
t.teardown(async () => {
await samlFederatedController.app.delete(app.id);
await connectionAPIController.deleteConnections({ tenant, product });
});
t.test('Federated SAML flow', async (t) => {
const relayStateFromSP = 'sp-saml-request-relay-state';
const requestXML = await fs.readFile(path.join(__dirname, '/data/request.xml'), 'utf8');
const responseXML = await fs.readFile(path.join(__dirname, '/data/response.xml'), 'utf8');
const samlRequestFromSP = Buffer.from(await deflateRawAsync(requestXML)).toString('base64');
const samlResponseFromIdP = Buffer.from(responseXML).toString('base64');
let jacksonRelayState: string | null = null;
t.test('Should be able to accept SAML Request from SP and generate SAML Request for IdP', async (t) => {
const response = await samlFederatedController.sso.getAuthorizeUrl({
request: samlRequestFromSP,
relayState: relayStateFromSP,
});
// Extract relay state created by Jackson
jacksonRelayState = new URL(response.redirectUrl).searchParams.get('RelayState');
t.ok(
response.redirectUrl?.startsWith(`${connection.idpMetadata.sso.redirectUrl}`),
'Should have a SSO URL that starts with IdP SSO URL'
);
t.ok(response.redirectUrl, 'Should have a redirect URL');
t.ok(response.redirectUrl?.includes('SAMLRequest'), 'Should have a SAMLRequest in the redirect URL');
t.ok(response.redirectUrl?.includes('RelayState'), 'Should have a RelayState in the redirect URL');
});
t.test('Should be able to accept SAML Response from IdP and generate SAML Response for SP', async (t) => {
const stubValidate = sinon.stub(saml, 'validate').resolves({
audience: 'https://saml.boxyhq.com',
claims: {
id: '00u3e3cmpdDydXdzV5d7',
email: 'kiran@boxyhq.com',
firstName: 'Kiran',
lastName: 'Krishnan',
'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier': 'kiran@boxyhq.com',
},
issuer: 'https://saml.example.com/entityid',
sessionIndex: '_a30730c45288bbc4986b',
});
const response = await oauthController.samlResponse({
SAMLResponse: samlResponseFromIdP,
RelayState: jacksonRelayState ?? '',
});
t.ok(response);
t.ok('responseForm' in response);
t.ok(
response.responseForm?.includes('SAMLResponse'),
'Should have a SAMLResponse in the response form'
);
t.ok(response.responseForm?.includes('RelayState'), 'Should have a RelayState in the response form');
const relayState = response.responseForm
? response.responseForm.match(/<input type="hidden" name="RelayState" value="(.*)"\/>/)?.[1]
: null;
t.match(relayState, relayStateFromSP, 'Should have the same relay state as the one sent by SP');
stubValidate.restore();
});
});
});

20799
package-lock.json generated

File diff suppressed because it is too large Load Diff