mirror of
https://github.com/HabitRPG/habitica.git
synced 2025-12-16 14:17:22 +01:00
272
test/api/unit/models/challenge.test.js
Normal file
272
test/api/unit/models/challenge.test.js
Normal file
@@ -0,0 +1,272 @@
|
||||
import { model as Challenge } from '../../../../website/server/models/challenge';
|
||||
import { model as Group } from '../../../../website/server/models/group';
|
||||
import { model as User } from '../../../../website/server/models/user';
|
||||
import * as Tasks from '../../../../website/server/models/task';
|
||||
import common from '../../../../website/common/';
|
||||
import { each, find } from 'lodash';
|
||||
|
||||
describe('Challenge Model', () => {
|
||||
let guild, leader, challenge, task;
|
||||
let tasksToTest = {
|
||||
habit: {
|
||||
text: 'test habit',
|
||||
type: 'habit',
|
||||
up: false,
|
||||
down: true,
|
||||
notes: 'test notes',
|
||||
},
|
||||
todo: {
|
||||
text: 'test todo',
|
||||
type: 'todo',
|
||||
notes: 'test notes',
|
||||
},
|
||||
daily: {
|
||||
text: 'test daily',
|
||||
type: 'daily',
|
||||
frequency: 'daily',
|
||||
everyX: 5,
|
||||
startDate: new Date(),
|
||||
notes: 'test notes',
|
||||
},
|
||||
reward: {
|
||||
text: 'test reward',
|
||||
type: 'reward',
|
||||
notes: 'test notes',
|
||||
},
|
||||
};
|
||||
|
||||
beforeEach(async () => {
|
||||
guild = new Group({
|
||||
name: 'test party',
|
||||
type: 'guild',
|
||||
});
|
||||
|
||||
leader = new User({
|
||||
guilds: [guild._id],
|
||||
});
|
||||
|
||||
guild.leader = leader._id;
|
||||
|
||||
challenge = new Challenge({
|
||||
name: 'Test Challenge',
|
||||
shortName: 'Test',
|
||||
leader: leader._id,
|
||||
group: guild._id,
|
||||
});
|
||||
|
||||
leader.challenges = [challenge._id];
|
||||
|
||||
await Promise.all([
|
||||
guild.save(),
|
||||
leader.save(),
|
||||
challenge.save(),
|
||||
]);
|
||||
});
|
||||
|
||||
each(tasksToTest, (taskValue, taskType) => {
|
||||
context(`${taskType}`, () => {
|
||||
beforeEach(async () => {
|
||||
task = new Tasks[`${taskType}`](Tasks.Task.sanitize(taskValue));
|
||||
task.challenge.id = challenge._id;
|
||||
await task.save();
|
||||
});
|
||||
|
||||
it('adds tasks to challenge and challenge members', async () => {
|
||||
await challenge.addTasks([task]);
|
||||
|
||||
const updatedLeader = await User.findOne({_id: leader._id});
|
||||
const updatedLeadersTasks = await Tasks.Task.find({_id: { $in: updatedLeader.tasksOrder[`${taskType}s`]}});
|
||||
const syncedTask = find(updatedLeadersTasks, function findNewTask (updatedLeadersTask) {
|
||||
return updatedLeadersTask.type === taskValue.type && updatedLeadersTask.text === taskValue.text;
|
||||
});
|
||||
|
||||
expect(syncedTask).to.exist;
|
||||
expect(syncedTask.notes).to.eql(task.notes);
|
||||
expect(syncedTask.tags[0]).to.eql(challenge._id);
|
||||
});
|
||||
|
||||
it('syncs a challenge to a user', async () => {
|
||||
await challenge.addTasks([task]);
|
||||
|
||||
let newMember = new User({
|
||||
guilds: [guild._id],
|
||||
});
|
||||
await newMember.save();
|
||||
|
||||
await challenge.syncToUser(newMember);
|
||||
|
||||
let updatedNewMember = await User.findById(newMember._id);
|
||||
let updatedNewMemberTasks = await Tasks.Task.find({_id: { $in: updatedNewMember.tasksOrder[`${taskType}s`]}});
|
||||
let syncedTask = find(updatedNewMemberTasks, function findNewTask (updatedNewMemberTask) {
|
||||
return updatedNewMemberTask.type === taskValue.type && updatedNewMemberTask.text === taskValue.text;
|
||||
});
|
||||
|
||||
expect(updatedNewMember.challenges).to.contain(challenge._id);
|
||||
expect(updatedNewMember.tags[7].id).to.equal(challenge._id);
|
||||
expect(updatedNewMember.tags[7].name).to.equal(challenge.shortName);
|
||||
expect(syncedTask).to.exist;
|
||||
expect(syncedTask.attribute).to.eql('str');
|
||||
});
|
||||
|
||||
it('syncs a challenge to a user with the existing task', async () => {
|
||||
await challenge.addTasks([task]);
|
||||
|
||||
let updatedLeader = await User.findOne({_id: leader._id});
|
||||
let updatedLeadersTasks = await Tasks.Task.find({_id: { $in: updatedLeader.tasksOrder[`${taskType}s`]}});
|
||||
let syncedTask = find(updatedLeadersTasks, function findNewTask (updatedLeadersTask) {
|
||||
return updatedLeadersTask.challenge.taskId === task._id;
|
||||
});
|
||||
|
||||
let createdAtBefore = syncedTask.createdAt;
|
||||
let attributeBefore = syncedTask.attribute;
|
||||
|
||||
let newTitle = 'newName';
|
||||
task.text = newTitle;
|
||||
task.attribute = 'int';
|
||||
await task.save();
|
||||
await challenge.syncToUser(leader);
|
||||
|
||||
updatedLeader = await User.findOne({_id: leader._id});
|
||||
updatedLeadersTasks = await Tasks.Task.find({_id: { $in: updatedLeader.tasksOrder[`${taskType}s`]}});
|
||||
|
||||
syncedTask = find(updatedLeadersTasks, function findNewTask (updatedLeadersTask) {
|
||||
return updatedLeadersTask.challenge.taskId === task._id;
|
||||
});
|
||||
|
||||
let createdAtAfter = syncedTask.createdAt;
|
||||
let attributeAfter = syncedTask.attribute;
|
||||
|
||||
expect(createdAtBefore).to.eql(createdAtAfter);
|
||||
expect(attributeBefore).to.eql(attributeAfter);
|
||||
expect(syncedTask.text).to.eql(newTitle);
|
||||
});
|
||||
|
||||
it('updates tasks to challenge and challenge members', async () => {
|
||||
let updatedTaskName = 'Updated Test Habit';
|
||||
await challenge.addTasks([task]);
|
||||
|
||||
let req = {
|
||||
body: { text: updatedTaskName },
|
||||
};
|
||||
|
||||
Tasks.Task.sanitize(req.body);
|
||||
_.assign(task, common.ops.updateTask(task.toObject(), req)[0]);
|
||||
|
||||
await challenge.updateTask(task);
|
||||
|
||||
let updatedLeader = await User.findOne({_id: leader._id});
|
||||
let updatedUserTask = await Tasks.Task.findById(updatedLeader.tasksOrder[`${taskType}s`][0]);
|
||||
|
||||
expect(updatedUserTask.text).to.equal(updatedTaskName);
|
||||
});
|
||||
|
||||
it('removes a tasks to challenge and challenge members', async () => {
|
||||
await challenge.addTasks([task]);
|
||||
await challenge.removeTask(task);
|
||||
|
||||
let updatedLeader = await User.findOne({_id: leader._id});
|
||||
let updatedUserTask = await Tasks.Task.findOne({_id: updatedLeader.tasksOrder[`${taskType}s`][0]}).exec();
|
||||
|
||||
expect(updatedUserTask.challenge.broken).to.equal('TASK_DELETED');
|
||||
});
|
||||
|
||||
it('unlinks and deletes challenge tasks for a user when remove-all is specified', async () => {
|
||||
await challenge.addTasks([task]);
|
||||
await challenge.unlinkTasks(leader, 'remove-all');
|
||||
|
||||
let updatedLeader = await User.findOne({_id: leader._id});
|
||||
let updatedLeadersTasks = await Tasks.Task.find({_id: { $in: updatedLeader.tasksOrder[`${taskType}s`]}});
|
||||
let syncedTask = find(updatedLeadersTasks, function findNewTask (updatedLeadersTask) {
|
||||
return updatedLeadersTask.type === taskValue.type && updatedLeadersTask.text === taskValue.text;
|
||||
});
|
||||
|
||||
expect(syncedTask).to.not.exist;
|
||||
});
|
||||
|
||||
it('unlinks and keeps challenge tasks for a user when keep-all is specified', async () => {
|
||||
await challenge.addTasks([task]);
|
||||
await challenge.unlinkTasks(leader, 'keep-all');
|
||||
|
||||
let updatedLeader = await User.findOne({_id: leader._id});
|
||||
let updatedLeadersTasks = await Tasks.Task.find({_id: { $in: updatedLeader.tasksOrder[`${taskType}s`]}});
|
||||
let syncedTask = find(updatedLeadersTasks, function findNewTask (updatedLeadersTask) {
|
||||
return updatedLeadersTask.type === taskValue.type && updatedLeadersTask.text === taskValue.text;
|
||||
});
|
||||
|
||||
expect(syncedTask).to.exist;
|
||||
expect(syncedTask.challenge._id).to.be.undefined;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
context('type specific updates', () => {
|
||||
it('updates habit specific field to challenge and challenge members', async () => {
|
||||
task = new Tasks.habit(Tasks.Task.sanitize(tasksToTest.habit)); // eslint-disable-line new-cap
|
||||
task.challenge.id = challenge._id;
|
||||
await task.save();
|
||||
|
||||
await challenge.addTasks([task]);
|
||||
|
||||
task.up = true;
|
||||
task.down = false;
|
||||
|
||||
await challenge.updateTask(task);
|
||||
|
||||
let updatedLeader = await User.findOne({_id: leader._id});
|
||||
let updatedUserTask = await Tasks.Task.findById(updatedLeader.tasksOrder.habits[0]);
|
||||
|
||||
expect(updatedUserTask.up).to.equal(true);
|
||||
expect(updatedUserTask.down).to.equal(false);
|
||||
});
|
||||
|
||||
it('updates todo specific field to challenge and challenge members', async () => {
|
||||
task = new Tasks.todo(Tasks.Task.sanitize(tasksToTest.todo)); // eslint-disable-line new-cap
|
||||
task.challenge.id = challenge._id;
|
||||
await task.save();
|
||||
|
||||
await challenge.addTasks([task]);
|
||||
|
||||
task.date = new Date();
|
||||
await challenge.updateTask(task);
|
||||
|
||||
let updatedLeader = await User.findOne({_id: leader._id});
|
||||
let updatedUserTask = await Tasks.Task.findById(updatedLeader.tasksOrder.todos[0]);
|
||||
|
||||
expect(updatedUserTask.date).to.exist;
|
||||
});
|
||||
|
||||
it('does not update checklists on the user task', async () => {
|
||||
task = new Tasks.todo(Tasks.Task.sanitize(tasksToTest.todo)); // eslint-disable-line new-cap
|
||||
task.challenge.id = challenge._id;
|
||||
await task.save();
|
||||
|
||||
await challenge.addTasks([task]);
|
||||
|
||||
task.checklist.push({
|
||||
text: 'a new checklist',
|
||||
});
|
||||
await challenge.updateTask(task);
|
||||
|
||||
let updatedLeader = await User.findOne({_id: leader._id});
|
||||
let updatedUserTask = await Tasks.Task.findById(updatedLeader.tasksOrder.todos[0]);
|
||||
|
||||
expect(updatedUserTask.checklist.toObject()).to.deep.equal([]);
|
||||
});
|
||||
|
||||
it('updates daily specific field to challenge and challenge members', async () => {
|
||||
task = new Tasks.daily(Tasks.Task.sanitize(tasksToTest.daily)); // eslint-disable-line new-cap
|
||||
task.challenge.id = challenge._id;
|
||||
await task.save();
|
||||
|
||||
await challenge.addTasks([task]);
|
||||
|
||||
task.everyX = 2;
|
||||
await challenge.updateTask(task);
|
||||
|
||||
let updatedLeader = await User.findOne({_id: leader._id});
|
||||
let updatedUserTask = await Tasks.Task.findById(updatedLeader.tasksOrder.dailys[0]);
|
||||
|
||||
expect(updatedUserTask.everyX).to.eql(2);
|
||||
});
|
||||
});
|
||||
});
|
||||
1891
test/api/unit/models/group.test.js
Normal file
1891
test/api/unit/models/group.test.js
Normal file
File diff suppressed because it is too large
Load Diff
281
test/api/unit/models/group_tasks.test.js
Normal file
281
test/api/unit/models/group_tasks.test.js
Normal file
@@ -0,0 +1,281 @@
|
||||
import { model as Challenge } from '../../../../website/server/models/challenge';
|
||||
import { model as Group } from '../../../../website/server/models/group';
|
||||
import { model as User } from '../../../../website/server/models/user';
|
||||
import * as Tasks from '../../../../website/server/models/task';
|
||||
import { each, find, findIndex } from 'lodash';
|
||||
|
||||
describe('Group Task Methods', () => {
|
||||
let guild, leader, challenge, task;
|
||||
let tasksToTest = {
|
||||
habit: {
|
||||
text: 'test habit',
|
||||
type: 'habit',
|
||||
up: false,
|
||||
down: true,
|
||||
},
|
||||
todo: {
|
||||
text: 'test todo',
|
||||
type: 'todo',
|
||||
},
|
||||
daily: {
|
||||
text: 'test daily',
|
||||
type: 'daily',
|
||||
frequency: 'daily',
|
||||
everyX: 5,
|
||||
startDate: new Date(),
|
||||
},
|
||||
reward: {
|
||||
text: 'test reward',
|
||||
type: 'reward',
|
||||
},
|
||||
};
|
||||
|
||||
function findLinkedTask (updatedLeadersTask) {
|
||||
return updatedLeadersTask.group.taskId === task._id;
|
||||
}
|
||||
|
||||
beforeEach(async () => {
|
||||
guild = new Group({
|
||||
name: 'test party',
|
||||
type: 'guild',
|
||||
});
|
||||
|
||||
leader = new User({
|
||||
guilds: [guild._id],
|
||||
});
|
||||
|
||||
guild.leader = leader._id;
|
||||
|
||||
challenge = new Challenge({
|
||||
name: 'Test Challenge',
|
||||
shortName: 'Test',
|
||||
leader: leader._id,
|
||||
group: guild._id,
|
||||
});
|
||||
|
||||
leader.challenges = [challenge._id];
|
||||
|
||||
await Promise.all([
|
||||
guild.save(),
|
||||
leader.save(),
|
||||
challenge.save(),
|
||||
]);
|
||||
});
|
||||
|
||||
each(tasksToTest, (taskValue, taskType) => {
|
||||
context(`${taskType}`, () => {
|
||||
beforeEach(async () => {
|
||||
task = new Tasks[`${taskType}`](Tasks.Task.sanitize(taskValue));
|
||||
task.group.id = guild._id;
|
||||
await task.save();
|
||||
if (task.checklist) {
|
||||
task.checklist.push({
|
||||
text: 'Checklist Item 1',
|
||||
completed: false,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
it('syncs an assigned task to a user', async () => {
|
||||
await guild.syncTask(task, leader);
|
||||
|
||||
let updatedLeader = await User.findOne({_id: leader._id});
|
||||
let tagIndex = findIndex(updatedLeader.tags, {id: guild._id});
|
||||
let newTag = updatedLeader.tags[tagIndex];
|
||||
|
||||
expect(newTag.id).to.equal(guild._id);
|
||||
expect(newTag.name).to.equal(guild.name);
|
||||
expect(newTag.group).to.equal(guild._id);
|
||||
});
|
||||
|
||||
it('create tags for a user when task is synced', async () => {
|
||||
await guild.syncTask(task, leader);
|
||||
|
||||
let updatedLeader = await User.findOne({_id: leader._id});
|
||||
let updatedLeadersTasks = await Tasks.Task.find({_id: { $in: updatedLeader.tasksOrder[`${taskType}s`]}});
|
||||
let syncedTask = find(updatedLeadersTasks, findLinkedTask);
|
||||
|
||||
expect(task.group.assignedUsers).to.contain(leader._id);
|
||||
expect(syncedTask).to.exist;
|
||||
});
|
||||
|
||||
it('syncs updated info for assigned task to a user', async () => {
|
||||
await guild.syncTask(task, leader);
|
||||
let updatedTaskName = 'Update Task name';
|
||||
task.text = updatedTaskName;
|
||||
await guild.syncTask(task, leader);
|
||||
|
||||
let updatedLeader = await User.findOne({_id: leader._id});
|
||||
let updatedLeadersTasks = await Tasks.Task.find({_id: { $in: updatedLeader.tasksOrder[`${taskType}s`]}});
|
||||
let syncedTask = find(updatedLeadersTasks, findLinkedTask);
|
||||
|
||||
expect(task.group.assignedUsers).to.contain(leader._id);
|
||||
expect(syncedTask).to.exist;
|
||||
expect(syncedTask.text).to.equal(task.text);
|
||||
});
|
||||
|
||||
it('syncs checklist items to an assigned user', async () => {
|
||||
await guild.syncTask(task, leader);
|
||||
|
||||
let updatedLeader = await User.findOne({_id: leader._id});
|
||||
let updatedLeadersTasks = await Tasks.Task.find({_id: { $in: updatedLeader.tasksOrder[`${taskType}s`]}});
|
||||
let syncedTask = find(updatedLeadersTasks, findLinkedTask);
|
||||
|
||||
if (task.type !== 'daily' && task.type !== 'todo') return;
|
||||
|
||||
expect(syncedTask.checklist.length).to.equal(task.checklist.length);
|
||||
expect(syncedTask.checklist[0].text).to.equal(task.checklist[0].text);
|
||||
});
|
||||
|
||||
describe('syncs updated info', async () => {
|
||||
let newMember;
|
||||
|
||||
beforeEach(async () => {
|
||||
newMember = new User({
|
||||
guilds: [guild._id],
|
||||
});
|
||||
await newMember.save();
|
||||
|
||||
await guild.syncTask(task, leader);
|
||||
await guild.syncTask(task, newMember);
|
||||
});
|
||||
|
||||
it('syncs updated info for assigned task to all users', async () => {
|
||||
let updatedTaskName = 'Update Task name';
|
||||
task.text = updatedTaskName;
|
||||
task.group.approval.required = true;
|
||||
|
||||
await guild.updateTask(task);
|
||||
|
||||
let updatedLeader = await User.findOne({_id: leader._id});
|
||||
let updatedLeadersTasks = await Tasks.Task.find({_id: { $in: updatedLeader.tasksOrder[`${taskType}s`]}});
|
||||
let syncedTask = find(updatedLeadersTasks, findLinkedTask);
|
||||
|
||||
let updatedMember = await User.findOne({_id: newMember._id});
|
||||
let updatedMemberTasks = await Tasks.Task.find({_id: { $in: updatedMember.tasksOrder[`${taskType}s`]}});
|
||||
let syncedMemberTask = find(updatedMemberTasks, findLinkedTask);
|
||||
|
||||
expect(task.group.assignedUsers).to.contain(leader._id);
|
||||
expect(syncedTask).to.exist;
|
||||
expect(syncedTask.text).to.equal(task.text);
|
||||
expect(syncedTask.group.approval.required).to.equal(true);
|
||||
|
||||
expect(task.group.assignedUsers).to.contain(newMember._id);
|
||||
expect(syncedMemberTask).to.exist;
|
||||
expect(syncedMemberTask.text).to.equal(task.text);
|
||||
expect(syncedMemberTask.group.approval.required).to.equal(true);
|
||||
});
|
||||
|
||||
it('syncs a new checklist item to all assigned users', async () => {
|
||||
if (task.type !== 'daily' && task.type !== 'todo') return;
|
||||
|
||||
let newCheckListItem = {
|
||||
text: 'Checklist Item 1',
|
||||
completed: false,
|
||||
};
|
||||
|
||||
task.checklist.push(newCheckListItem);
|
||||
|
||||
await guild.updateTask(task, {newCheckListItem});
|
||||
|
||||
let updatedLeader = await User.findOne({_id: leader._id});
|
||||
let updatedLeadersTasks = await Tasks.Task.find({_id: { $in: updatedLeader.tasksOrder[`${taskType}s`]}});
|
||||
let syncedTask = find(updatedLeadersTasks, findLinkedTask);
|
||||
|
||||
let updatedMember = await User.findOne({_id: newMember._id});
|
||||
let updatedMemberTasks = await Tasks.Task.find({_id: { $in: updatedMember.tasksOrder[`${taskType}s`]}});
|
||||
let syncedMemberTask = find(updatedMemberTasks, findLinkedTask);
|
||||
|
||||
expect(syncedTask.checklist.length).to.equal(task.checklist.length);
|
||||
expect(syncedTask.checklist[1].text).to.equal(task.checklist[1].text);
|
||||
expect(syncedMemberTask.checklist.length).to.equal(task.checklist.length);
|
||||
expect(syncedMemberTask.checklist[1].text).to.equal(task.checklist[1].text);
|
||||
});
|
||||
|
||||
it('syncs updated info for checklist in assigned task to all users when flag is passed', async () => {
|
||||
if (task.type !== 'daily' && task.type !== 'todo') return;
|
||||
|
||||
let updateCheckListText = 'Updated checklist item';
|
||||
if (task.checklist) {
|
||||
task.checklist[0].text = updateCheckListText;
|
||||
}
|
||||
|
||||
await guild.updateTask(task, {updateCheckListItems: [task.checklist[0]]});
|
||||
|
||||
let updatedLeader = await User.findOne({_id: leader._id});
|
||||
let updatedLeadersTasks = await Tasks.Task.find({_id: { $in: updatedLeader.tasksOrder[`${taskType}s`]}});
|
||||
let syncedTask = find(updatedLeadersTasks, findLinkedTask);
|
||||
|
||||
let updatedMember = await User.findOne({_id: newMember._id});
|
||||
let updatedMemberTasks = await Tasks.Task.find({_id: { $in: updatedMember.tasksOrder[`${taskType}s`]}});
|
||||
let syncedMemberTask = find(updatedMemberTasks, findLinkedTask);
|
||||
|
||||
expect(syncedTask.checklist.length).to.equal(task.checklist.length);
|
||||
expect(syncedTask.checklist[0].text).to.equal(updateCheckListText);
|
||||
expect(syncedMemberTask.checklist.length).to.equal(task.checklist.length);
|
||||
expect(syncedMemberTask.checklist[0].text).to.equal(updateCheckListText);
|
||||
});
|
||||
|
||||
it('removes a checklist item in assigned task to all users when flag is passed with checklist id', async () => {
|
||||
if (task.type !== 'daily' && task.type !== 'todo') return;
|
||||
|
||||
await guild.updateTask(task, {removedCheckListItemId: task.checklist[0].id});
|
||||
|
||||
let updatedLeader = await User.findOne({_id: leader._id});
|
||||
let updatedLeadersTasks = await Tasks.Task.find({_id: { $in: updatedLeader.tasksOrder[`${taskType}s`]}});
|
||||
let syncedTask = find(updatedLeadersTasks, findLinkedTask);
|
||||
|
||||
let updatedMember = await User.findOne({_id: newMember._id});
|
||||
let updatedMemberTasks = await Tasks.Task.find({_id: { $in: updatedMember.tasksOrder[`${taskType}s`]}});
|
||||
let syncedMemberTask = find(updatedMemberTasks, findLinkedTask);
|
||||
|
||||
expect(syncedTask.checklist.length).to.equal(0);
|
||||
expect(syncedMemberTask.checklist.length).to.equal(0);
|
||||
});
|
||||
});
|
||||
|
||||
it('removes an assigned task and unlinks assignees', async () => {
|
||||
await guild.syncTask(task, leader);
|
||||
await guild.removeTask(task);
|
||||
|
||||
let updatedLeader = await User.findOne({_id: leader._id});
|
||||
let updatedLeadersTasks = await Tasks.Task.find({_id: { $in: updatedLeader.tasksOrder[`${taskType}s`]}});
|
||||
let syncedTask = find(updatedLeadersTasks, findLinkedTask);
|
||||
|
||||
expect(syncedTask.group.broken).to.equal('TASK_DELETED');
|
||||
});
|
||||
|
||||
it('unlinks and deletes group tasks for a user when remove-all is specified', async () => {
|
||||
await guild.syncTask(task, leader);
|
||||
await guild.unlinkTask(task, leader, 'remove-all');
|
||||
|
||||
let updatedLeader = await User.findOne({_id: leader._id});
|
||||
let updatedLeadersTasks = await Tasks.Task.find({_id: { $in: updatedLeader.tasksOrder[`${taskType}s`]}});
|
||||
let syncedTask = find(updatedLeadersTasks, findLinkedTask);
|
||||
|
||||
expect(task.group.assignedUsers).to.not.contain(leader._id);
|
||||
expect(syncedTask).to.not.exist;
|
||||
});
|
||||
|
||||
it('unlinks and keeps group tasks for a user when keep-all is specified', async () => {
|
||||
await guild.syncTask(task, leader);
|
||||
|
||||
let updatedLeader = await User.findOne({_id: leader._id});
|
||||
let updatedLeadersTasks = await Tasks.Task.find({_id: { $in: updatedLeader.tasksOrder[`${taskType}s`]}});
|
||||
let syncedTask = find(updatedLeadersTasks, findLinkedTask);
|
||||
|
||||
await guild.unlinkTask(task, leader, 'keep-all');
|
||||
|
||||
updatedLeader = await User.findOne({_id: leader._id});
|
||||
updatedLeadersTasks = await Tasks.Task.find({_id: { $in: updatedLeader.tasksOrder[`${taskType}s`]}});
|
||||
let updatedSyncedTask = find(updatedLeadersTasks, function findUpdatedLinkedTask (updatedLeadersTask) {
|
||||
return updatedLeadersTask._id === syncedTask._id;
|
||||
});
|
||||
|
||||
expect(task.group.assignedUsers).to.not.contain(leader._id);
|
||||
expect(updatedSyncedTask).to.exist;
|
||||
expect(updatedSyncedTask.group._id).to.be.undefined;
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
174
test/api/unit/models/task.test.js
Normal file
174
test/api/unit/models/task.test.js
Normal file
@@ -0,0 +1,174 @@
|
||||
import { model as Challenge } from '../../../../website/server/models/challenge';
|
||||
import { model as Group } from '../../../../website/server/models/group';
|
||||
import { model as User } from '../../../../website/server/models/user';
|
||||
import * as Tasks from '../../../../website/server/models/task';
|
||||
import { InternalServerError } from '../../../../website/server/libs/errors';
|
||||
import { each } from 'lodash';
|
||||
import { generateHistory } from '../../../helpers/api-unit.helper.js';
|
||||
|
||||
describe('Task Model', () => {
|
||||
let guild, leader, challenge, task;
|
||||
let tasksToTest = {
|
||||
habit: {
|
||||
text: 'test habit',
|
||||
type: 'habit',
|
||||
up: false,
|
||||
down: true,
|
||||
},
|
||||
daily: {
|
||||
text: 'test daily',
|
||||
type: 'daily',
|
||||
frequency: 'daily',
|
||||
everyX: 5,
|
||||
startDate: new Date(),
|
||||
},
|
||||
};
|
||||
|
||||
beforeEach(async () => {
|
||||
guild = new Group({
|
||||
name: 'test guild',
|
||||
type: 'guild',
|
||||
});
|
||||
|
||||
leader = new User({
|
||||
guilds: [guild._id],
|
||||
});
|
||||
|
||||
guild.leader = leader._id;
|
||||
|
||||
challenge = new Challenge({
|
||||
name: 'Test Challenge',
|
||||
shortName: 'Test',
|
||||
leader: leader._id,
|
||||
group: guild._id,
|
||||
});
|
||||
|
||||
leader.challenges = [challenge._id];
|
||||
|
||||
await Promise.all([
|
||||
guild.save(),
|
||||
leader.save(),
|
||||
challenge.save(),
|
||||
]);
|
||||
});
|
||||
|
||||
each(tasksToTest, (taskValue, taskType) => {
|
||||
context(`${taskType}`, () => {
|
||||
beforeEach(async () => {
|
||||
task = new Tasks[`${taskType}`](Tasks.Task.sanitize(taskValue));
|
||||
task.challenge.id = challenge._id;
|
||||
task.history = generateHistory(396);
|
||||
await task.save();
|
||||
});
|
||||
|
||||
it('preens challenge tasks history when scored', async () => {
|
||||
let historyLengthBeforePreen = task.history.length;
|
||||
|
||||
await task.scoreChallengeTask(1.2);
|
||||
|
||||
let updatedTask = await Tasks.Task.findOne({_id: task._id});
|
||||
|
||||
expect(historyLengthBeforePreen).to.be.greaterThan(updatedTask.history.length);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Static Methods', () => {
|
||||
describe('findByIdOrAlias', () => {
|
||||
let taskWithAlias, user;
|
||||
|
||||
beforeEach(async () => {
|
||||
user = new User();
|
||||
await user.save();
|
||||
|
||||
taskWithAlias = new Tasks.todo({ // eslint-disable-line new-cap
|
||||
text: 'some text',
|
||||
alias: 'short-name',
|
||||
userId: user.id,
|
||||
});
|
||||
await taskWithAlias.save();
|
||||
|
||||
sandbox.spy(Tasks.Task, 'findOne');
|
||||
});
|
||||
|
||||
it('throws an error if task identifier is not passed in', async () => {
|
||||
try {
|
||||
await Tasks.Task.findByIdOrAlias(null, user._id);
|
||||
throw new Error('No exception when Id is None');
|
||||
} catch (err) {
|
||||
expect(err).to.exist;
|
||||
expect(err).to.eql(new InternalServerError('Task identifier is a required argument'));
|
||||
}
|
||||
});
|
||||
|
||||
it('throws an error if user identifier is not passed in', async () => {
|
||||
try {
|
||||
await Tasks.Task.findByIdOrAlias(taskWithAlias._id);
|
||||
throw new Error('No exception when user_id is undefined');
|
||||
} catch (err) {
|
||||
expect(err).to.exist;
|
||||
expect(err).to.eql(new InternalServerError('User identifier is a required argument'));
|
||||
}
|
||||
});
|
||||
|
||||
it('returns task by id', async () => {
|
||||
let foundTodo = await Tasks.Task.findByIdOrAlias(taskWithAlias._id, user._id);
|
||||
|
||||
expect(foundTodo.text).to.eql(taskWithAlias.text);
|
||||
});
|
||||
|
||||
it('returns task by alias', async () => {
|
||||
let foundTodo = await Tasks.Task.findByIdOrAlias(taskWithAlias.alias, user._id);
|
||||
|
||||
expect(foundTodo.text).to.eql(taskWithAlias.text);
|
||||
});
|
||||
|
||||
it('scopes alias lookup to user', async () => {
|
||||
await Tasks.Task.findByIdOrAlias(taskWithAlias.alias, user._id);
|
||||
|
||||
expect(Tasks.Task.findOne).to.be.calledOnce;
|
||||
expect(Tasks.Task.findOne).to.be.calledWithMatch({
|
||||
alias: taskWithAlias.alias,
|
||||
userId: user._id,
|
||||
});
|
||||
});
|
||||
|
||||
it('returns null if task cannot be found', async () => {
|
||||
let foundTask = await Tasks.Task.findByIdOrAlias('not-found', user._id);
|
||||
|
||||
expect(foundTask).to.eql(null);
|
||||
});
|
||||
|
||||
it('accepts additional query parameters', async () => {
|
||||
await Tasks.Task.findByIdOrAlias(taskWithAlias.alias, user._id, { foo: 'bar' });
|
||||
|
||||
expect(Tasks.Task.findOne).to.be.calledOnce;
|
||||
expect(Tasks.Task.findOne).to.be.calledWithMatch({
|
||||
foo: 'bar',
|
||||
alias: taskWithAlias.alias,
|
||||
userId: user._id,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('sanitizeUserChallengeTask ', () => {
|
||||
});
|
||||
|
||||
describe('sanitizeChecklist ', () => {
|
||||
});
|
||||
|
||||
describe('sanitizeReminder ', () => {
|
||||
});
|
||||
|
||||
describe('fromJSONV2 ', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('Instance Methods', () => {
|
||||
describe('scoreChallengeTask', () => {
|
||||
});
|
||||
|
||||
describe('toJSONV2', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
535
test/api/unit/models/user.test.js
Normal file
535
test/api/unit/models/user.test.js
Normal file
@@ -0,0 +1,535 @@
|
||||
import moment from 'moment';
|
||||
import { model as User } from '../../../../website/server/models/user';
|
||||
import { model as Group } from '../../../../website/server/models/group';
|
||||
import common from '../../../../website/common';
|
||||
|
||||
describe('User Model', () => {
|
||||
it('keeps user._tmp when calling .toJSON', () => {
|
||||
let user = new User({
|
||||
auth: {
|
||||
local: {
|
||||
username: 'username',
|
||||
lowerCaseUsername: 'username',
|
||||
email: 'email@email.email',
|
||||
salt: 'salt',
|
||||
hashed_password: 'hashed_password', // eslint-disable-line camelcase
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
user._tmp = {ok: true};
|
||||
user._nonTmp = {ok: true};
|
||||
|
||||
expect(user._tmp).to.eql({ok: true});
|
||||
expect(user._nonTmp).to.eql({ok: true});
|
||||
|
||||
let toObject = user.toObject();
|
||||
let toJSON = user.toJSON();
|
||||
|
||||
expect(toObject).to.not.have.keys('_tmp');
|
||||
expect(toObject).to.not.have.keys('_nonTmp');
|
||||
|
||||
expect(toJSON).to.have.any.key('_tmp');
|
||||
expect(toJSON._tmp).to.eql({ok: true});
|
||||
expect(toJSON).to.not.have.keys('_nonTmp');
|
||||
});
|
||||
|
||||
it('can add computed stats to a JSONified user object', () => {
|
||||
let user = new User();
|
||||
let userToJSON = user.toJSON();
|
||||
|
||||
expect(userToJSON.stats.maxMP).to.not.exist;
|
||||
expect(userToJSON.stats.maxHealth).to.not.exist;
|
||||
expect(userToJSON.stats.toNextLevel).to.not.exist;
|
||||
|
||||
User.addComputedStatsToJSONObj(userToJSON.stats, userToJSON);
|
||||
|
||||
expect(userToJSON.stats.maxMP).to.exist;
|
||||
expect(userToJSON.stats.maxHealth).to.equal(common.maxHealth);
|
||||
expect(userToJSON.stats.toNextLevel).to.equal(common.tnl(user.stats.lvl));
|
||||
});
|
||||
|
||||
it('can transform user object without mongoose helpers', async () => {
|
||||
let user = new User();
|
||||
await user.save();
|
||||
let userToJSON = await User.findById(user._id).lean().exec();
|
||||
|
||||
expect(userToJSON.stats.maxMP).to.not.exist;
|
||||
expect(userToJSON.stats.maxHealth).to.not.exist;
|
||||
expect(userToJSON.stats.toNextLevel).to.not.exist;
|
||||
expect(userToJSON.id).to.not.exist;
|
||||
|
||||
User.transformJSONUser(userToJSON);
|
||||
|
||||
expect(userToJSON.id).to.equal(userToJSON._id);
|
||||
expect(userToJSON.stats.maxMP).to.not.exist;
|
||||
expect(userToJSON.stats.maxHealth).to.not.exist;
|
||||
expect(userToJSON.stats.toNextLevel).to.not.exist;
|
||||
});
|
||||
|
||||
it('can transform user object without mongoose helpers (including computed stats)', async () => {
|
||||
let user = new User();
|
||||
await user.save();
|
||||
let userToJSON = await User.findById(user._id).lean().exec();
|
||||
|
||||
expect(userToJSON.stats.maxMP).to.not.exist;
|
||||
expect(userToJSON.stats.maxHealth).to.not.exist;
|
||||
expect(userToJSON.stats.toNextLevel).to.not.exist;
|
||||
|
||||
User.transformJSONUser(userToJSON, true);
|
||||
|
||||
expect(userToJSON.id).to.equal(userToJSON._id);
|
||||
expect(userToJSON.stats.maxMP).to.exist;
|
||||
expect(userToJSON.stats.maxHealth).to.equal(common.maxHealth);
|
||||
expect(userToJSON.stats.toNextLevel).to.equal(common.tnl(user.stats.lvl));
|
||||
});
|
||||
|
||||
context('notifications', () => {
|
||||
it('can add notifications without data', () => {
|
||||
let user = new User();
|
||||
|
||||
user.addNotification('CRON');
|
||||
|
||||
let userToJSON = user.toJSON();
|
||||
expect(user.notifications.length).to.equal(1);
|
||||
expect(userToJSON.notifications[0]).to.have.all.keys(['data', 'id', 'type', 'seen']);
|
||||
expect(userToJSON.notifications[0].type).to.equal('CRON');
|
||||
expect(userToJSON.notifications[0].data).to.eql({});
|
||||
expect(userToJSON.notifications[0].seen).to.eql(false);
|
||||
});
|
||||
|
||||
it('removes invalid notifications when calling toJSON', () => {
|
||||
let user = new User();
|
||||
|
||||
user.notifications = [
|
||||
null, // invalid, not an object
|
||||
{seen: true}, // invalid, no type or id
|
||||
{id: 123}, // invalid, no type
|
||||
// {type: 'ABC'}, // invalid, no id, not included here because the id would be added automatically
|
||||
{type: 'ABC', id: '123'}, // valid
|
||||
];
|
||||
|
||||
const userToJSON = user.toJSON();
|
||||
expect(userToJSON.notifications.length).to.equal(1);
|
||||
|
||||
expect(userToJSON.notifications[0]).to.have.all.keys(['data', 'id', 'type', 'seen']);
|
||||
expect(userToJSON.notifications[0].type).to.equal('ABC');
|
||||
expect(userToJSON.notifications[0].id).to.equal('123');
|
||||
});
|
||||
|
||||
it('can add notifications with data and already marked as seen', () => {
|
||||
let user = new User();
|
||||
|
||||
user.addNotification('CRON', {field: 1}, true);
|
||||
|
||||
let userToJSON = user.toJSON();
|
||||
expect(user.notifications.length).to.equal(1);
|
||||
expect(userToJSON.notifications[0]).to.have.all.keys(['data', 'id', 'type', 'seen']);
|
||||
expect(userToJSON.notifications[0].type).to.equal('CRON');
|
||||
expect(userToJSON.notifications[0].data).to.eql({field: 1});
|
||||
expect(userToJSON.notifications[0].seen).to.eql(true);
|
||||
});
|
||||
|
||||
context('static push method', () => {
|
||||
it('adds notifications for a single member via static method', async () => {
|
||||
let user = new User();
|
||||
await user.save();
|
||||
|
||||
await User.pushNotification({_id: user._id}, 'CRON');
|
||||
|
||||
user = await User.findOne({_id: user._id}).exec();
|
||||
|
||||
let userToJSON = user.toJSON();
|
||||
expect(user.notifications.length).to.equal(1);
|
||||
expect(userToJSON.notifications[0]).to.have.all.keys(['data', 'id', 'type', 'seen']);
|
||||
expect(userToJSON.notifications[0].type).to.equal('CRON');
|
||||
expect(userToJSON.notifications[0].data).to.eql({});
|
||||
});
|
||||
|
||||
it('validates notifications via static method', async () => {
|
||||
let user = new User();
|
||||
await user.save();
|
||||
|
||||
expect(User.pushNotification({_id: user._id}, 'BAD_TYPE')).to.eventually.be.rejected;
|
||||
expect(User.pushNotification({_id: user._id}, 'CRON', null, 'INVALID_SEEN')).to.eventually.be.rejected;
|
||||
});
|
||||
|
||||
it('adds notifications without data for all given users via static method', async () => {
|
||||
let user = new User();
|
||||
let otherUser = new User();
|
||||
await Promise.all([user.save(), otherUser.save()]);
|
||||
|
||||
await User.pushNotification({_id: {$in: [user._id, otherUser._id]}}, 'CRON');
|
||||
|
||||
user = await User.findOne({_id: user._id}).exec();
|
||||
|
||||
let userToJSON = user.toJSON();
|
||||
expect(user.notifications.length).to.equal(1);
|
||||
expect(userToJSON.notifications[0]).to.have.all.keys(['data', 'id', 'type', 'seen']);
|
||||
expect(userToJSON.notifications[0].type).to.equal('CRON');
|
||||
expect(userToJSON.notifications[0].data).to.eql({});
|
||||
expect(userToJSON.notifications[0].seen).to.eql(false);
|
||||
|
||||
user = await User.findOne({_id: otherUser._id}).exec();
|
||||
|
||||
userToJSON = user.toJSON();
|
||||
expect(user.notifications.length).to.equal(1);
|
||||
expect(userToJSON.notifications[0]).to.have.all.keys(['data', 'id', 'type', 'seen']);
|
||||
expect(userToJSON.notifications[0].type).to.equal('CRON');
|
||||
expect(userToJSON.notifications[0].data).to.eql({});
|
||||
expect(userToJSON.notifications[0].seen).to.eql(false);
|
||||
});
|
||||
|
||||
it('adds notifications with data and seen status for all given users via static method', async () => {
|
||||
let user = new User();
|
||||
let otherUser = new User();
|
||||
await Promise.all([user.save(), otherUser.save()]);
|
||||
|
||||
await User.pushNotification({_id: {$in: [user._id, otherUser._id]}}, 'CRON', {field: 1}, true);
|
||||
|
||||
user = await User.findOne({_id: user._id}).exec();
|
||||
|
||||
let userToJSON = user.toJSON();
|
||||
expect(user.notifications.length).to.equal(1);
|
||||
expect(userToJSON.notifications[0]).to.have.all.keys(['data', 'id', 'type', 'seen']);
|
||||
expect(userToJSON.notifications[0].type).to.equal('CRON');
|
||||
expect(userToJSON.notifications[0].data).to.eql({field: 1});
|
||||
expect(userToJSON.notifications[0].seen).to.eql(true);
|
||||
|
||||
user = await User.findOne({_id: otherUser._id}).exec();
|
||||
|
||||
userToJSON = user.toJSON();
|
||||
expect(user.notifications.length).to.equal(1);
|
||||
expect(userToJSON.notifications[0]).to.have.all.keys(['data', 'id', 'type', 'seen']);
|
||||
expect(userToJSON.notifications[0].type).to.equal('CRON');
|
||||
expect(userToJSON.notifications[0].data).to.eql({field: 1});
|
||||
expect(userToJSON.notifications[0].seen).to.eql(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
context('isSubscribed', () => {
|
||||
let user;
|
||||
beforeEach(() => {
|
||||
user = new User();
|
||||
});
|
||||
|
||||
|
||||
it('returns false if user does not have customer id', () => {
|
||||
expect(user.isSubscribed()).to.be.undefined;
|
||||
});
|
||||
|
||||
it('returns true if user does not have plan.dateTerminated', () => {
|
||||
user.purchased.plan.customerId = 'test-id';
|
||||
|
||||
expect(user.isSubscribed()).to.be.true;
|
||||
});
|
||||
|
||||
it('returns true if user if plan.dateTerminated is after today', () => {
|
||||
user.purchased.plan.customerId = 'test-id';
|
||||
user.purchased.plan.dateTerminated = moment().add(1, 'days').toDate();
|
||||
|
||||
expect(user.isSubscribed()).to.be.true;
|
||||
});
|
||||
|
||||
it('returns false if user if plan.dateTerminated is before today', () => {
|
||||
user.purchased.plan.customerId = 'test-id';
|
||||
user.purchased.plan.dateTerminated = moment().subtract(1, 'days').toDate();
|
||||
|
||||
expect(user.isSubscribed()).to.be.false;
|
||||
});
|
||||
});
|
||||
|
||||
context('canGetGems', () => {
|
||||
let user;
|
||||
let group;
|
||||
beforeEach(() => {
|
||||
user = new User();
|
||||
let leader = new User();
|
||||
group = new Group({
|
||||
name: 'test',
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
leader: leader._id,
|
||||
});
|
||||
});
|
||||
|
||||
it('returns true if user is not subscribed', async () => {
|
||||
expect(await user.canGetGems()).to.equal(true);
|
||||
});
|
||||
|
||||
it('returns true if user is not subscribed with a group plan', async () => {
|
||||
user.purchased.plan.customerId = 123;
|
||||
expect(await user.canGetGems()).to.equal(true);
|
||||
});
|
||||
|
||||
it('returns true if user is subscribed with a group plan', async () => {
|
||||
user.purchased.plan.customerId = 'group-plan';
|
||||
expect(await user.canGetGems()).to.equal(true);
|
||||
});
|
||||
|
||||
it('returns true if user is part of a group', async () => {
|
||||
user.guilds.push(group._id);
|
||||
expect(await user.canGetGems()).to.equal(true);
|
||||
});
|
||||
|
||||
it('returns true if user is part of a group with a subscription', async () => {
|
||||
user.guilds.push(group._id);
|
||||
user.purchased.plan.customerId = 'group-plan';
|
||||
group.purchased.plan.customerId = 123;
|
||||
await group.save();
|
||||
expect(await user.canGetGems()).to.equal(true);
|
||||
});
|
||||
|
||||
it('returns true if leader is part of a group with a subscription and canGetGems: false', async () => {
|
||||
user.guilds.push(group._id);
|
||||
user.purchased.plan.customerId = 'group-plan';
|
||||
group.purchased.plan.customerId = 123;
|
||||
group.leader = user._id;
|
||||
group.leaderOnly.getGems = true;
|
||||
await group.save();
|
||||
expect(await user.canGetGems()).to.equal(true);
|
||||
});
|
||||
|
||||
it('returns true if user is part of a group with no subscription but canGetGems: false', async () => {
|
||||
user.guilds.push(group._id);
|
||||
user.purchased.plan.customerId = 'group-plan';
|
||||
group.leaderOnly.getGems = true;
|
||||
await group.save();
|
||||
expect(await user.canGetGems()).to.equal(true);
|
||||
});
|
||||
|
||||
it('returns false if user is part of a group with a subscription and canGetGems: false', async () => {
|
||||
user.guilds.push(group._id);
|
||||
user.purchased.plan.customerId = 'group-plan';
|
||||
group.purchased.plan.customerId = 123;
|
||||
group.leaderOnly.getGems = true;
|
||||
await group.save();
|
||||
expect(await user.canGetGems()).to.equal(false);
|
||||
});
|
||||
});
|
||||
|
||||
context('hasNotCancelled', () => {
|
||||
let user;
|
||||
beforeEach(() => {
|
||||
user = new User();
|
||||
});
|
||||
|
||||
|
||||
it('returns false if user does not have customer id', () => {
|
||||
expect(user.hasNotCancelled()).to.be.undefined;
|
||||
});
|
||||
|
||||
it('returns true if user does not have plan.dateTerminated', () => {
|
||||
user.purchased.plan.customerId = 'test-id';
|
||||
|
||||
expect(user.hasNotCancelled()).to.be.true;
|
||||
});
|
||||
|
||||
it('returns false if user if plan.dateTerminated is after today', () => {
|
||||
user.purchased.plan.customerId = 'test-id';
|
||||
user.purchased.plan.dateTerminated = moment().add(1, 'days').toDate();
|
||||
|
||||
expect(user.hasNotCancelled()).to.be.false;
|
||||
});
|
||||
|
||||
it('returns false if user if plan.dateTerminated is before today', () => {
|
||||
user.purchased.plan.customerId = 'test-id';
|
||||
user.purchased.plan.dateTerminated = moment().subtract(1, 'days').toDate();
|
||||
|
||||
expect(user.hasNotCancelled()).to.be.false;
|
||||
});
|
||||
});
|
||||
|
||||
context('pre-save hook', () => {
|
||||
it('does not try to award achievements when achievements or items not selected in query', async () => {
|
||||
let user = new User();
|
||||
user = await user.save(); // necessary for user.isSelected to work correctly
|
||||
|
||||
// Create conditions for the Beast Master achievement to be awarded
|
||||
user.achievements.beastMasterCount = 3;
|
||||
expect(user.achievements.beastMaster).to.not.equal(true); // verify that it was not awarded initially
|
||||
|
||||
user = await user.save();
|
||||
// verify that it's been awarded
|
||||
expect(user.achievements.beastMaster).to.equal(true);
|
||||
|
||||
// reset the user
|
||||
user.achievements.beastMasterCount = 0;
|
||||
user.achievements.beastMaster = false;
|
||||
|
||||
user = await user.save();
|
||||
// verify it's been removed
|
||||
expect(user.achievements.beastMaster).to.equal(false);
|
||||
|
||||
// fetch the user without selecting the 'items' field
|
||||
user = await User.findById(user._id).select('-items').exec();
|
||||
expect(user.isSelected('items')).to.equal(false);
|
||||
|
||||
// create the conditions for the beast master achievement but this time it should not be awarded
|
||||
user.achievements.beastMasterCount = 3;
|
||||
user = await user.save();
|
||||
expect(user.achievements.beastMaster).to.equal(false);
|
||||
|
||||
// reset
|
||||
user.achievements.beastMasterCount = 0;
|
||||
user = await user.save();
|
||||
|
||||
// this time with achievements not selected
|
||||
user = await User.findById(user._id).select('-achievements').exec();
|
||||
expect(user.isSelected('achievements')).to.equal(false);
|
||||
user.achievements.beastMasterCount = 3;
|
||||
user = await user.save();
|
||||
expect(user.achievements.beastMaster).to.not.equal(true);
|
||||
});
|
||||
|
||||
context('manage unallocated stats points notifications', () => {
|
||||
it('doesn\'t add a notification if there are no points to allocate', async () => {
|
||||
let user = new User();
|
||||
|
||||
user.flags.classSelected = true;
|
||||
user.preferences.disableClasses = false;
|
||||
user.stats.class = 'warrior';
|
||||
user = await user.save(); // necessary for user.isSelected to work correctly
|
||||
|
||||
const oldNotificationsCount = user.notifications.length;
|
||||
|
||||
user.stats.points = 0;
|
||||
user = await user.save();
|
||||
|
||||
expect(user.notifications.length).to.equal(oldNotificationsCount);
|
||||
});
|
||||
|
||||
it('removes a notification if there are no more points to allocate', async () => {
|
||||
let user = new User();
|
||||
|
||||
user.flags.classSelected = true;
|
||||
user.preferences.disableClasses = false;
|
||||
user.stats.class = 'warrior';
|
||||
user.stats.points = 9;
|
||||
user = await user.save(); // necessary for user.isSelected to work correctly
|
||||
|
||||
expect(user.notifications[0].type).to.equal('UNALLOCATED_STATS_POINTS');
|
||||
const oldNotificationsCount = user.notifications.length;
|
||||
|
||||
user.stats.points = 0;
|
||||
user = await user.save();
|
||||
|
||||
expect(user.notifications.length).to.equal(oldNotificationsCount - 1);
|
||||
});
|
||||
|
||||
it('adds a notification if there are points to allocate', async () => {
|
||||
let user = new User();
|
||||
user.flags.classSelected = true;
|
||||
user.preferences.disableClasses = false;
|
||||
user.stats.class = 'warrior';
|
||||
user = await user.save(); // necessary for user.isSelected to work correctly
|
||||
const oldNotificationsCount = user.notifications.length;
|
||||
|
||||
user.stats.points = 9;
|
||||
user = await user.save();
|
||||
|
||||
expect(user.notifications.length).to.equal(oldNotificationsCount + 1);
|
||||
expect(user.notifications[0].type).to.equal('UNALLOCATED_STATS_POINTS');
|
||||
expect(user.notifications[0].data.points).to.equal(9);
|
||||
});
|
||||
|
||||
it('adds a notification if the points to allocate have changed', async () => {
|
||||
let user = new User();
|
||||
user.stats.points = 9;
|
||||
user.flags.classSelected = true;
|
||||
user.preferences.disableClasses = false;
|
||||
user.stats.class = 'warrior';
|
||||
user = await user.save(); // necessary for user.isSelected to work correctly
|
||||
|
||||
const oldNotificationsCount = user.notifications.length;
|
||||
const oldNotificationsUUID = user.notifications[0].id;
|
||||
expect(user.notifications[0].type).to.equal('UNALLOCATED_STATS_POINTS');
|
||||
expect(user.notifications[0].data.points).to.equal(9);
|
||||
|
||||
user.stats.points = 11;
|
||||
user = await user.save();
|
||||
|
||||
expect(user.notifications.length).to.equal(oldNotificationsCount);
|
||||
expect(user.notifications[0].type).to.equal('UNALLOCATED_STATS_POINTS');
|
||||
expect(user.notifications[0].data.points).to.equal(11);
|
||||
expect(user.notifications[0].id).to.not.equal(oldNotificationsUUID);
|
||||
});
|
||||
|
||||
it('does not add a notification if the user has disabled classes', async () => {
|
||||
let user = new User();
|
||||
user.stats.points = 9;
|
||||
user.flags.classSelected = true;
|
||||
user.preferences.disableClasses = true;
|
||||
user.stats.class = 'warrior';
|
||||
user = await user.save(); // necessary for user.isSelected to work correctly
|
||||
|
||||
const oldNotificationsCount = user.notifications.length;
|
||||
|
||||
user.stats.points = 9;
|
||||
user = await user.save();
|
||||
|
||||
expect(user.notifications.length).to.equal(oldNotificationsCount);
|
||||
});
|
||||
|
||||
it('does not add a notification if the user has not selected a class', async () => {
|
||||
let user = new User();
|
||||
user.stats.points = 9;
|
||||
user.flags.classSelected = false;
|
||||
user.stats.class = 'warrior';
|
||||
user = await user.save(); // necessary for user.isSelected to work correctly
|
||||
|
||||
const oldNotificationsCount = user.notifications.length;
|
||||
|
||||
user.stats.points = 9;
|
||||
user = await user.save();
|
||||
|
||||
expect(user.notifications.length).to.equal(oldNotificationsCount);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
context('days missed', () => {
|
||||
// http://forbrains.co.uk/international_tools/earth_timezones
|
||||
let user;
|
||||
|
||||
beforeEach(() => {
|
||||
user = new User();
|
||||
});
|
||||
|
||||
it('should not cron early when going back a timezone', () => {
|
||||
const yesterday = moment('2017-12-05T00:00:00.000-06:00'); // 11 pm on 4 Texas
|
||||
const timezoneOffset = moment().zone('-06:00').zone();
|
||||
user.lastCron = yesterday;
|
||||
user.preferences.timezoneOffset = timezoneOffset;
|
||||
|
||||
const today = moment('2017-12-06T00:00:00.000-06:00'); // 11 pm on 4 Texas
|
||||
const req = {};
|
||||
req.header = () => {
|
||||
return timezoneOffset + 60;
|
||||
};
|
||||
|
||||
const {daysMissed} = user.daysUserHasMissed(today, req);
|
||||
|
||||
expect(daysMissed).to.eql(0);
|
||||
});
|
||||
|
||||
it('should not cron early when going back a timezone with a custom day start', () => {
|
||||
const yesterday = moment('2017-12-05T02:00:00.000-08:00');
|
||||
const timezoneOffset = moment().zone('-08:00').zone();
|
||||
user.lastCron = yesterday;
|
||||
user.preferences.timezoneOffset = timezoneOffset;
|
||||
user.preferences.dayStart = 2;
|
||||
|
||||
const today = moment('2017-12-06T02:00:00.000-08:00');
|
||||
const req = {};
|
||||
req.header = () => {
|
||||
return timezoneOffset + 60;
|
||||
};
|
||||
|
||||
const {daysMissed} = user.daysUserHasMissed(today, req);
|
||||
|
||||
expect(daysMissed).to.eql(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
21
test/api/unit/models/userNotification.test.js
Normal file
21
test/api/unit/models/userNotification.test.js
Normal file
@@ -0,0 +1,21 @@
|
||||
import { model as UserNotification } from '../../../../website/server/models/userNotification';
|
||||
|
||||
describe('UserNotification Model', () => {
|
||||
context('convertNotificationsToSafeJson', () => {
|
||||
it('converts an array of notifications to a safe version', () => {
|
||||
const notifications = [
|
||||
null, // invalid, not an object
|
||||
{seen: true}, // invalid, no type or id
|
||||
{id: 123}, // invalid, no type
|
||||
{type: 'ABC'}, // invalid, no id
|
||||
new UserNotification({type: 'ABC', id: 123}), // valid
|
||||
];
|
||||
|
||||
const notificationsToJSON = UserNotification.convertNotificationsToSafeJson(notifications);
|
||||
expect(notificationsToJSON.length).to.equal(1);
|
||||
expect(notificationsToJSON[0]).to.have.all.keys(['data', 'id', 'type', 'seen']);
|
||||
expect(notificationsToJSON[0].type).to.equal('ABC');
|
||||
expect(notificationsToJSON[0].id).to.equal('123');
|
||||
});
|
||||
});
|
||||
});
|
||||
323
test/api/unit/models/webhook.test.js
Normal file
323
test/api/unit/models/webhook.test.js
Normal file
@@ -0,0 +1,323 @@
|
||||
import { model as Webhook } from '../../../../website/server/models/webhook';
|
||||
import { BadRequest } from '../../../../website/server/libs/errors';
|
||||
import { v4 as generateUUID } from 'uuid';
|
||||
import apiError from '../../../../website/server/libs/apiError';
|
||||
|
||||
describe('Webhook Model', () => {
|
||||
context('Instance Methods', () => {
|
||||
describe('#formatOptions', () => {
|
||||
let res;
|
||||
|
||||
beforeEach(() => {
|
||||
res = {
|
||||
t: sandbox.spy(),
|
||||
};
|
||||
});
|
||||
context('type is taskActivity', () => {
|
||||
let config;
|
||||
|
||||
beforeEach(() => {
|
||||
config = {
|
||||
type: 'taskActivity',
|
||||
url: 'https//exmaple.com/endpoint',
|
||||
options: {
|
||||
created: true,
|
||||
updated: true,
|
||||
deleted: true,
|
||||
scored: true,
|
||||
checklistScored: true,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
it('it provides default values for options', () => {
|
||||
delete config.options;
|
||||
|
||||
let wh = new Webhook(config);
|
||||
|
||||
wh.formatOptions(res);
|
||||
|
||||
expect(wh.options).to.eql({
|
||||
checklistScored: false,
|
||||
created: false,
|
||||
updated: false,
|
||||
deleted: false,
|
||||
scored: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('provides missing task options', () => {
|
||||
delete config.options.created;
|
||||
|
||||
let wh = new Webhook(config);
|
||||
|
||||
wh.formatOptions(res);
|
||||
|
||||
expect(wh.options).to.eql({
|
||||
checklistScored: true,
|
||||
created: false,
|
||||
updated: true,
|
||||
deleted: true,
|
||||
scored: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('discards additional options', () => {
|
||||
config.options.foo = 'another option';
|
||||
|
||||
let wh = new Webhook(config);
|
||||
|
||||
wh.formatOptions(res);
|
||||
|
||||
expect(wh.options.foo).to.not.exist;
|
||||
expect(wh.options).to.eql({
|
||||
checklistScored: true,
|
||||
created: true,
|
||||
updated: true,
|
||||
deleted: true,
|
||||
scored: true,
|
||||
});
|
||||
});
|
||||
|
||||
['created', 'updated', 'deleted', 'scored', 'checklistScored'].forEach((option) => {
|
||||
it(`validates that ${option} is a boolean`, (done) => {
|
||||
config.options[option] = 'not a boolean';
|
||||
|
||||
try {
|
||||
let wh = new Webhook(config);
|
||||
|
||||
wh.formatOptions(res);
|
||||
} catch (err) {
|
||||
expect(err).to.be.an.instanceOf(BadRequest);
|
||||
expect(res.t).to.be.calledOnce;
|
||||
expect(res.t).to.be.calledWith('webhookBooleanOption', { option });
|
||||
done();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
context('type is userActivity', () => {
|
||||
let config;
|
||||
|
||||
beforeEach(() => {
|
||||
config = {
|
||||
type: 'userActivity',
|
||||
url: 'https//exmaple.com/endpoint',
|
||||
options: {
|
||||
petHatched: true,
|
||||
mountRaised: true,
|
||||
leveledUp: true,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
it('it provides default values for options', () => {
|
||||
delete config.options;
|
||||
|
||||
let wh = new Webhook(config);
|
||||
|
||||
wh.formatOptions(res);
|
||||
|
||||
expect(wh.options).to.eql({
|
||||
petHatched: false,
|
||||
mountRaised: false,
|
||||
leveledUp: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('provides missing user options', () => {
|
||||
delete config.options.petHatched;
|
||||
|
||||
let wh = new Webhook(config);
|
||||
|
||||
wh.formatOptions(res);
|
||||
|
||||
expect(wh.options).to.eql({
|
||||
petHatched: false,
|
||||
mountRaised: true,
|
||||
leveledUp: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('discards additional options', () => {
|
||||
config.options.foo = 'another option';
|
||||
|
||||
let wh = new Webhook(config);
|
||||
|
||||
wh.formatOptions(res);
|
||||
|
||||
expect(wh.options.foo).to.not.exist;
|
||||
expect(wh.options).to.eql({
|
||||
petHatched: true,
|
||||
mountRaised: true,
|
||||
leveledUp: true,
|
||||
});
|
||||
});
|
||||
|
||||
['petHatched', 'petHatched', 'leveledUp'].forEach((option) => {
|
||||
it(`validates that ${option} is a boolean`, (done) => {
|
||||
config.options[option] = 'not a boolean';
|
||||
|
||||
try {
|
||||
let wh = new Webhook(config);
|
||||
|
||||
wh.formatOptions(res);
|
||||
} catch (err) {
|
||||
expect(err).to.be.an.instanceOf(BadRequest);
|
||||
expect(res.t).to.be.calledOnce;
|
||||
expect(res.t).to.be.calledWith('webhookBooleanOption', { option });
|
||||
done();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
context('type is questActivity', () => {
|
||||
let config;
|
||||
|
||||
beforeEach(() => {
|
||||
config = {
|
||||
type: 'questActivity',
|
||||
url: 'https//exmaple.com/endpoint',
|
||||
options: {
|
||||
questStarted: true,
|
||||
questFinished: true,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
it('it provides default values for options', () => {
|
||||
delete config.options;
|
||||
|
||||
let wh = new Webhook(config);
|
||||
|
||||
wh.formatOptions(res);
|
||||
|
||||
expect(wh.options).to.eql({
|
||||
questStarted: false,
|
||||
questFinished: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('provides missing user options', () => {
|
||||
delete config.options.questStarted;
|
||||
|
||||
let wh = new Webhook(config);
|
||||
|
||||
wh.formatOptions(res);
|
||||
|
||||
expect(wh.options).to.eql({
|
||||
questStarted: false,
|
||||
questFinished: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('discards additional options', () => {
|
||||
config.options.foo = 'another option';
|
||||
|
||||
let wh = new Webhook(config);
|
||||
|
||||
wh.formatOptions(res);
|
||||
|
||||
expect(wh.options.foo).to.not.exist;
|
||||
expect(wh.options).to.eql({
|
||||
questStarted: true,
|
||||
questFinished: true,
|
||||
});
|
||||
});
|
||||
|
||||
['questStarted', 'questFinished'].forEach((option) => {
|
||||
it(`validates that ${option} is a boolean`, (done) => {
|
||||
config.options[option] = 'not a boolean';
|
||||
|
||||
try {
|
||||
let wh = new Webhook(config);
|
||||
|
||||
wh.formatOptions(res);
|
||||
} catch (err) {
|
||||
expect(err).to.be.an.instanceOf(BadRequest);
|
||||
expect(res.t).to.be.calledOnce;
|
||||
expect(res.t).to.be.calledWith('webhookBooleanOption', { option });
|
||||
done();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
context('type is groupChatReceived', () => {
|
||||
let config;
|
||||
|
||||
beforeEach(() => {
|
||||
config = {
|
||||
type: 'groupChatReceived',
|
||||
url: 'https//exmaple.com/endpoint',
|
||||
options: {
|
||||
groupId: generateUUID(),
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
it('creates options', () => {
|
||||
let wh = new Webhook(config);
|
||||
|
||||
wh.formatOptions(res);
|
||||
|
||||
expect(wh.options).to.eql(config.options);
|
||||
});
|
||||
|
||||
it('discards additional objects', () => {
|
||||
config.options.foo = 'another thing';
|
||||
|
||||
let wh = new Webhook(config);
|
||||
|
||||
wh.formatOptions(res);
|
||||
|
||||
expect(wh.options.foo).to.not.exist;
|
||||
expect(wh.options).to.eql({
|
||||
groupId: config.options.groupId,
|
||||
});
|
||||
});
|
||||
|
||||
it('requires groupId option to be a uuid', (done) => {
|
||||
config.options.groupId = 'not a uuid';
|
||||
|
||||
try {
|
||||
let wh = new Webhook(config);
|
||||
|
||||
wh.formatOptions(res);
|
||||
} catch (err) {
|
||||
expect(err).to.be.an.instanceOf(BadRequest);
|
||||
|
||||
expect(err.message).to.eql(apiError('groupIdRequired'));
|
||||
done();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
context('type is globalActivity', () => {
|
||||
let config;
|
||||
|
||||
beforeEach(() => {
|
||||
config = {
|
||||
type: 'globalActivity',
|
||||
url: 'https//exmaple.com/endpoint',
|
||||
options: { },
|
||||
};
|
||||
});
|
||||
|
||||
it('discards additional objects', () => {
|
||||
config.options.foo = 'another thing';
|
||||
|
||||
let wh = new Webhook(config);
|
||||
|
||||
wh.formatOptions(res);
|
||||
|
||||
expect(wh.options.foo).to.not.exist;
|
||||
expect(wh.options).to.eql({});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user