From 77c927ac5958afb207c58ec8ad5a3f714ceca31a Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Wed, 8 Nov 2017 16:35:38 -0700 Subject: [PATCH] added migration, migration tests :tada: --- migrations/1496771633_tags.js | 2 +- migrations/1510174676_user_status.js | 211 ++++++++++++++++++ package.json | 5 +- .../migrations/1510174676_user_status.js | 203 +++++++++++++++++ 4 files changed, 419 insertions(+), 2 deletions(-) create mode 100644 migrations/1510174676_user_status.js create mode 100644 test/server/migrations/1510174676_user_status.js diff --git a/migrations/1496771633_tags.js b/migrations/1496771633_tags.js index be3fa2577..2480ed6c6 100644 --- a/migrations/1496771633_tags.js +++ b/migrations/1496771633_tags.js @@ -17,7 +17,7 @@ module.exports = { }} ]); - // If no comments were found, nothing needes to be done! + // If no comments were found, nothing needs to be done! if (comments.length <= 0) { return; } diff --git a/migrations/1510174676_user_status.js b/migrations/1510174676_user_status.js new file mode 100644 index 000000000..4bd52639b --- /dev/null +++ b/migrations/1510174676_user_status.js @@ -0,0 +1,211 @@ +const UserModel = require('../models/user'); +const merge = require('lodash/merge'); + +const getUserBatch = async () => { + let query = { + status: { + $in: [ + 'ACTIVE', + 'BANNED', + 'PENDING', + 'APPROVED' + ] + } + }; + + // Find all the users that need migrating. + return UserModel.collection.find(query); +}; + +module.exports = { + async up() { + + const created_at = Date.now(); + + // Create a new batch operation. + let bulk = UserModel.collection.initializeUnorderedBulkOp(); + + // Get the first batch of users. + let cursor = await getUserBatch(); + + while (await cursor.hasNext()) { + const user = await cursor.next(); + + const {id, status, canEditName, suspension, disabled} = user; + + let update = { + $unset: { + canEditName: '', + suspension: '', + disabled: '', + }, + $set: { + status: { + + // The username status is specific to each case. + username: { + history: [] + }, + + // The user is not banned by default. + banned: { + status: false, + history: [] + }, + + // The user is not suspended by default. + suspension: { + until: null, + history: [] + }, + }, + updated_at: created_at + }, + }; + + if (disabled) { + update = merge(update, { + $set: { + status: { + banned: { + status: true, + history: [{ + status: true, + created_at + }] + }, + } + } + }); + } + + // If the user has an "until" property of their suspension, then we need + // to reflect that in the new status object. + if (suspension && suspension.until !== null) { + update = merge(update, { + $set: { + status: { + suspension: { + until: suspension.until, + history: [{ + until: suspension.until, + created_at, + }] + } + } + } + }); + } + + switch (status) { + case 'ACTIVE': + if (canEditName) { + update = merge(update, { + $set: { + status: { + username: { + status: 'UNSET', + history: [{ + status: 'UNSET', + created_at + }] + } + } + } + }); + } else { + update = merge(update, { + $set: { + status: { + username: { + status: 'SET', + history: [{ + status: 'SET', + created_at + }] + } + } + } + }); + } + break; + case 'BANNED': + if (canEditName) { + update = merge(update, { + $set: { + status: { + username: { + status: 'REJECTED', + history: [{ + status: 'REJECTED', + created_at + }] + } + } + } + }); + } else { + update = merge(update, { + $set: { + status: { + banned: { + status: true, + history: [{ + status: true, + created_at + }] + }, + username: { + status: 'SET', + history: [{ + status: 'SET', + created_at + }] + } + } + } + }); + } + break; + case 'PENDING': + update = merge(update, { + $set: { + status: { + username: { + status: 'CHANGED', + history: [{ + status: 'CHANGED', + created_at + }] + } + } + } + }); + break; + case 'APPROVED': + update = merge(update, { + $set: { + status: { + username: { + status: 'APPROVED', + history: [{ + status: 'APPROVED', + created_at + }] + } + } + } + }); + break; + default: + throw new Error(`${status} is an invalid status`); + } + + bulk.find({id}).updateOne(update); + } + + // Execute the bulk update operation. + await bulk.execute(); + } +}; + diff --git a/package.json b/package.json index 0975c89b5..1b57bfb09 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ }, "talk": { "migration": { - "minVersion": 1507322128 + "minVersion": 1510174676 } }, "repository": { @@ -228,5 +228,8 @@ "post-checkout": [], "post-merge": [] } + }, + "release": { + "analyzeCommits": "simple-commit-message" } } diff --git a/test/server/migrations/1510174676_user_status.js b/test/server/migrations/1510174676_user_status.js new file mode 100644 index 000000000..0e5f1df35 --- /dev/null +++ b/test/server/migrations/1510174676_user_status.js @@ -0,0 +1,203 @@ +const migration = require('../../../migrations/1510174676_user_status'); +const UserModel = require('../../../models/user'); + +const chai = require('chai'); +chai.use(require('chai-datetime')); +const {expect} = chai; + +describe('migration.1510174676_user_status', () => { + describe('active user', () => { + beforeEach(async () => { + await UserModel.collection.insert({ + id: '123', + username: 'Kirk', + lowercaseUsername: 'kirk', + status: 'ACTIVE', + canEditName: false + }); + }); + + it('completes the migration', async () => { + + let user = await UserModel.collection.findOne({id: '123'}); + + expect(user).to.have.property('status', 'ACTIVE'); + expect(user).to.have.property('canEditName', false); + + // Perform the migration. + await migration.up(); + + user = await UserModel.collection.findOne({id: '123'}); + + // Check that it was correct. + expect(user).to.have.property('status'); + expect(user.status).to.have.property('username'); + expect(user.status.username).to.have.property('status', 'SET'); + expect(user.status.username.history).to.have.length(1); + }); + }); + + describe('social user', () => { + beforeEach(async () => { + await UserModel.collection.insert({ + id: '123', + username: 'Kirk', + lowercaseUsername: 'kirk', + status: 'ACTIVE', + canEditName: true + }); + }); + + it('completes the migration', async () => { + + let user = await UserModel.collection.findOne({id: '123'}); + + expect(user).to.have.property('status', 'ACTIVE'); + expect(user).to.have.property('canEditName', true); + + // Perform the migration. + await migration.up(); + + user = await UserModel.collection.findOne({id: '123'}); + + // Check that it was correct. + expect(user).to.have.property('status'); + expect(user.status).to.have.property('username'); + expect(user.status.username).to.have.property('status', 'UNSET'); + expect(user.status.username.history).to.have.length(1); + }); + }); + + describe('rejected username', () => { + beforeEach(async () => { + await UserModel.collection.insert({ + id: '123', + username: 'Kirk', + lowercaseUsername: 'kirk', + status: 'BANNED', + canEditName: true + }); + }); + + it('completes the migration', async () => { + + let user = await UserModel.collection.findOne({id: '123'}); + + expect(user).to.have.property('status'); + expect(user.status).to.equal('BANNED'); + expect(user.canEditName).to.equal(true); + + // Perform the migration. + await migration.up(); + + user = await UserModel.collection.findOne({id: '123'}); + + // Check that it was correct. + expect(user).to.have.property('status'); + expect(user.status).to.have.property('banned'); + expect(user.status.banned).to.have.property('status', false); + expect(user.status.username).to.have.property('status', 'REJECTED'); + expect(user.status.username.history).to.have.length(1); + }); + }); + + describe('approved username', () => { + beforeEach(async () => { + await UserModel.collection.insert({ + id: '123', + username: 'Kirk', + lowercaseUsername: 'kirk', + status: 'APPROVED', + canEditName: false + }); + }); + + it('completes the migration', async () => { + + let user = await UserModel.collection.findOne({id: '123'}); + + expect(user).to.have.property('status'); + expect(user.status).to.equal('APPROVED'); + expect(user.canEditName).to.equal(false); + + // Perform the migration. + await migration.up(); + + user = await UserModel.collection.findOne({id: '123'}); + + // Check that it was correct. + expect(user).to.have.property('status'); + expect(user.status).to.have.property('banned'); + expect(user.status.banned).to.have.property('status', false); + expect(user.status.username).to.have.property('status', 'APPROVED'); + expect(user.status.username.history).to.have.length(1); + }); + }); + + describe('suspended user', () => { + beforeEach(async () => { + await UserModel.collection.insert({ + id: '123', + username: 'Kirk', + lowercaseUsername: 'kirk', + status: 'ACTIVE', + suspension: { + until: new Date() + } + }); + }); + + it('completes the migration', async () => { + + let user = await UserModel.collection.findOne({id: '123'}); + + expect(user).to.have.property('suspension'); + expect(user.suspension).to.have.property('until'); + expect(user.suspension.until).to.not.be.null; + + const until = user.suspension.until; + + // Perform the migration. + await migration.up(); + + user = await UserModel.collection.findOne({id: '123'}); + + // Check that it was correct. + expect(user).to.have.property('status'); + expect(user.status).to.have.property('suspension'); + expect(user.status.suspension).to.have.property('until'); + expect(user.status.suspension.until).to.not.be.null; + expect(user.status.suspension.until).to.be.withinTime(new Date(until.getTime() - 1000), new Date(until.getTime() + 1000)); + }); + }); + + describe('banned user', () => { + beforeEach(async () => { + await UserModel.collection.insert({ + id: '123', + username: 'Kirk', + lowercaseUsername: 'kirk', + status: 'BANNED', + canEditName: false + }); + }); + + it('completes the migration', async () => { + + let user = await UserModel.collection.findOne({id: '123'}); + + expect(user).to.have.property('status'); + expect(user.status).to.equal('BANNED'); + + // Perform the migration. + await migration.up(); + + user = await UserModel.collection.findOne({id: '123'}); + + // Check that it was correct. + expect(user).to.have.property('status'); + expect(user.status).to.have.property('banned'); + expect(user.status.banned).to.have.property('status', true); + }); + }); +});