UNPKG

smc-hub

Version:

CoCalc: Backend webserver component

968 lines (949 loc) 25.6 kB
// Generated by CoffeeScript 2.5.1 (function() { //######################################################################## // This file is part of CoCalc: Copyright © 2020 Sagemath, Inc. // License: AGPLv3 s.t. "Commons Clause" – see LICENSE.md for details //######################################################################## /* TESTING of user queries specifically involving changefeeds - part 1 -- accounts, file_use, project_log COPYRIGHT : (c) 2017 SageMath, Inc. LICENSE : AGPLv3 */ var async, changefeed_series, create_accounts, create_projects, db, expect, misc, pgtest, setup, teardown; async = require('async'); expect = require('expect'); pgtest = require('./pgtest'); db = void 0; setup = function(cb) { return pgtest.setup(function(err) { db = pgtest.db; return cb(err); }); }; teardown = pgtest.teardown; ({create_accounts, create_projects, changefeed_series} = pgtest); misc = require('smc-util/misc'); describe('test the accounts table changefeed', function() { var account_id, changefeed_id; this.timeout(10000); before(setup); after(teardown); account_id = void 0; changefeed_id = misc.uuid(); return it('writes to user accounts table and verify that change automatically appears in changefeed', function(done) { return async.series([ function(cb) { return db.create_account({ first_name: "Sage", last_name: "Math", created_by: "1.2.3.4", email_address: "sage@example.com", password_hash: "blah", cb: function(err, x) { account_id = x; return cb(err); } }); }, function(cb) { return db.user_query({ account_id: account_id, query: { accounts: [ { account_id: account_id, first_name: null } ] }, changes: changefeed_id, cb: changefeed_series([ function(x, cb) { return db.user_query({ account_id: account_id, query: { accounts: { account_id: account_id, first_name: 'SAGE!' } }, cb: cb }); }, function(x, cb) { expect(x).toEqual({ action: 'insert', new_val: { account_id: account_id, first_name: 'SAGE!' } }); return db.delete_account({ account_id: account_id, cb: cb }); }, function(x, cb) { expect(x).toEqual({ action: 'delete', old_val: { account_id: account_id } }); return db.user_query_cancel_changefeed({ id: changefeed_id, cb: cb }); } ], cb) }); } ], function(err) { if (err) { return done(err); } else { return done(); } }); }); }); describe('test changefeeds involving the file_use table on one project with one user', function() { var accounts, projects; this.timeout(10000); before(setup); after(teardown); accounts = []; projects = []; it('create accounts and projects', function(done) { return async.series([ function(cb) { return create_accounts(1, function(err, x) { accounts = x; return cb(); }); }, function(cb) { return create_projects(1, accounts[0], function(err, x) { projects.push(...x); return cb(err); }); } ], done); }); return it('tests a changefeed on the file_use table for a single project', function(done) { var id, obj, obj2, t0, t1, t2; id = misc.uuid(); t0 = new Date(); t1 = t2 = void 0; obj = { project_id: projects[0], path: 'foo.txt', users: { [`${accounts[0]}`]: { 'read': t0 }, last_edited: t0 } }; obj2 = { project_id: projects[0], path: 'foo2.txt', last_edited: new Date() }; return db.user_query({ account_id: accounts[0], query: { file_use: [ { id: null, project_id: projects[0], path: null, users: null, last_edited: null } ] }, changes: id, cb: changefeed_series([ function(x, cb) { expect(x).toEqual({ file_use: [] // how it starts }); return db.user_query({ account_id: accounts[0], query: { file_use: obj }, cb: cb }); }, function(x, cb) { obj.id = db.sha1(obj.project_id, obj.path); expect(x).toEqual({ action: 'insert', new_val: obj }); // now mutate it by chatting on it t1 = new Date(); return db.user_query({ account_id: accounts[0], query: { file_use: { project_id: projects[0], path: 'foo.txt', users: { [`${accounts[0]}`]: { 'chat': t1 } } } }, cb: cb }); }, function(x, cb) { // note how chat gets recursively MERGED IN -- not replacing users. (so tricky under the hood to implement...) obj.users[`${accounts[0]}`].chat = t1; expect(x.action).toEqual('update'); expect(x.new_val.users).toEqual(obj.users); // now mutate it by updating last_edited t2 = new Date(); return db.user_query({ account_id: accounts[0], query: { file_use: { project_id: projects[0], path: 'foo.txt', last_edited: t2 } }, cb: cb }); }, function(x, cb) { obj.last_edited = t2; expect(x.action).toEqual('update'); expect(x.new_val.last_edited).toEqual(obj.last_edited); // add a second file_use entry return db.user_query({ account_id: accounts[0], query: { file_use: obj2 }, cb: cb }); }, function(x, cb) { obj2.id = db.sha1(obj2.project_id, obj2.path); expect(x).toEqual({ action: 'insert', new_val: obj2 }); // now delete the first entry (not through file_use, but directly) return db._query({ query: "DELETE FROM file_use", where: { 'id = $': obj.id }, cb: cb }); }, function(x, cb) { expect(x).toEqual({ action: "delete", old_val: { id: obj.id, project_id: projects[0] } }); // and the second return db._query({ query: "DELETE FROM file_use", where: { 'id = $': obj2.id }, cb: cb }); }, function(x, cb) { expect(x).toEqual({ action: "delete", old_val: { id: obj2.id, project_id: projects[0] } }); return db.user_query_cancel_changefeed({ id: id, cb: cb }); } ], done) }); }); }); describe('test file_use changefeeds with multiple projects', function() { var accounts, projects; before(setup); after(teardown); accounts = []; projects = []; it('create account and projects', function(done) { return async.series([ function(cb) { return create_accounts(2, function(err, x) { accounts = x; return cb(); }); }, function(cb) { return create_projects(2, accounts[0], function(err, x) { projects.push(...x); return cb(err); }); }, function(cb) { return create_projects(1, accounts[1], function(err, x) { projects.push(...x); return cb(err); }); } ], done); }); return it('insert into file_use for three separate projects', function(done) { var i, id, len, obj, t, x; id = misc.uuid(); t = [misc.minutes_ago(10), misc.minutes_ago(5), misc.minutes_ago(3)]; obj = [ { project_id: projects[0], path: 'file-in-project0.txt', users: { [`${accounts[0]}`]: { 'read': t[0] } } }, { project_id: projects[1], path: 'file-in-project1.txt', users: { [`${accounts[0]}`]: { 'chat': t[1] } } }, { project_id: projects[2], path: 'file-in-project2.txt', users: { [`${accounts[1]}`]: { 'chat': t[2] } } } ]; for (i = 0, len = obj.length; i < len; i++) { x = obj[i]; x.id = db.sha1(x.project_id, x.path); } return db.user_query({ account_id: accounts[0], query: { file_use: [ { id: null, project_id: null, path: null, users: null, last_edited: null } ] }, changes: id, cb: changefeed_series([ function(x, cb) { expect(x).toEqual({ file_use: [] // how it starts }); return db.user_query({ // insert first object account_id: accounts[0], query: { file_use: obj[0] }, cb: cb }); }, function(x, cb) { expect(x).toEqual({ action: 'insert', new_val: obj[0] }); return db.user_query({ // insert second object account_id: accounts[0], query: { file_use: obj[1] }, cb: cb }); }, function(x, cb) { expect(x).toEqual({ action: 'insert', new_val: obj[1] }); return db.user_query({ // insert third object, which should NOT trigger change account_id: accounts[1], query: { file_use: obj[2] }, cb: () => { obj[1].last_edited = new Date(); return db.user_query({ // insert second object again, modified account_id: accounts[0], query: { file_use: obj[1] }, cb: cb }); } }); }, function(x, cb) { expect(x.action).toEqual('update'); expect(x.new_val.last_edited).toEqual(obj[1].last_edited); return cb(); } ], done) }); }); }); describe('modifying a single file_use record in various ways', function() { var accounts, projects; before(setup); after(teardown); accounts = []; projects = []; it('create account and projects', function(done) { return async.series([ function(cb) { // 2 accounts return create_accounts(2, function(err, x) { accounts = x; return cb(); }); }, function(cb) { // 1 project with both accounts using it return create_projects(1, accounts, function(err, x) { projects.push(...x); return cb(err); }); } ], done); }); return it('insert a file_use entry and modify in various ways', function(done) { var changefeed_id, obj, time; changefeed_id = misc.uuid(); time = new Date(); obj = { project_id: projects[0], path: 'file-in-project0.txt', users: { [`${accounts[0]}`]: { 'read': time } } }; obj.id = db.sha1(obj.project_id, obj.path); return db.user_query({ account_id: accounts[0], query: { file_use: [ { id: null, project_id: null, path: null, users: null, last_edited: null } ] }, changes: changefeed_id, cb: changefeed_series([ function(x, cb) { expect(x).toEqual({ file_use: [] // how it starts }); cb(); return db.user_query({ account_id: accounts[0], query: { file_use: obj }, cb: cb }); }, function(x, cb) { expect(x).toEqual({ action: 'insert', new_val: obj }); obj.last_edited = new Date(); return db.user_query({ account_id: accounts[0], query: { file_use: obj }, cb: cb }); }, function(x, cb) { expect(x.action).toEqual('update'); expect(x.new_val.last_edited).toEqual(obj.last_edited); obj.users[accounts[0]].chat = new Date(); return db.user_query({ account_id: accounts[0], query: { file_use: obj }, cb: cb }); }, function(x, cb) { expect(x.action).toEqual('update'); expect(x.new_val.users).toEqual(obj.users); obj.users[accounts[1]] = { seen: new Date() }; return db.user_query({ account_id: accounts[1], query: { file_use: obj }, cb: cb }); }, function(x, cb) { expect(x.action).toEqual('update'); expect(x.new_val.users).toEqual(obj.users); obj.users[accounts[0]] = { chat: new Date(), read: new Date() }; return db.user_query({ account_id: accounts[0], query: { file_use: obj }, cb: cb }); }, function(x, cb) { expect(x.action).toEqual('update'); expect(x.new_val.users).toEqual(obj.users); return db._query({ query: "DELETE FROM file_use", where: { 'id = $': obj.id }, cb: cb }); }, function(x, cb) { expect(x).toEqual({ action: "delete", old_val: { id: obj.id, project_id: obj.project_id } }); // Cancel the changefeed... return db.user_query_cancel_changefeed({ id: changefeed_id, cb: cb }); }, function(x, cb) { expect(x).toEqual({ action: 'close' }); return cb(); } ], done) }); }); }); describe('test changefeeds with project_log', function() { var accounts, projects; before(setup); after(teardown); accounts = []; projects = []; it('create accounts and projects', function(done) { return async.series([ function(cb) { return create_accounts(2, function(err, x) { accounts = x; return cb(); }); }, function(cb) { return create_projects(2, accounts[0], function(err, x) { projects.push(...x); return cb(err); }); }, function(cb) { return create_projects(1, accounts[1], function(err, x) { projects.push(...x); return cb(err); }); } ], done); }); it('tests a simple project_log changefeed on a single project', function(done) { var changefeed_id, obj; obj = { id: misc.uuid(), project_id: projects[0], time: new Date(), account_id: accounts[0], event: { sample: 'thing' } }; changefeed_id = misc.uuid(); return db.user_query({ account_id: accounts[0], query: { project_log: [ { id: null, project_id: projects[0], time: null, account_id: null, event: null } ] }, changes: changefeed_id, cb: changefeed_series([ function(x, cb) { expect(x).toEqual({ project_log: [] // how it starts }); return db.user_query({ account_id: accounts[0], query: { project_log: obj }, cb: cb }); }, function(x, cb) { expect(x).toEqual({ action: 'insert', new_val: obj }); return db.user_query_cancel_changefeed({ id: changefeed_id, cb: cb }); }, function(x, cb) { // check that the next thing we get is close, not the insert we just made above. expect(x).toEqual({ action: 'close' }); return cb(); } ], done) }); }); return it("tests a project_log changefeed on all of a user's project", function(done) { var changefeed_id, obj1, obj2; obj1 = { id: misc.uuid(), project_id: projects[1], time: new Date(), account_id: accounts[0], event: { stuff: [1, 2] } }; obj2 = { id: misc.uuid(), project_id: projects[2], time: new Date(), account_id: accounts[1], event: { user: 1 } }; changefeed_id = misc.uuid(); return db.user_query({ account_id: accounts[0], query: { project_log: [ { id: null, project_id: null, time: null, account_id: null, event: null } ] }, changes: changefeed_id, cb: changefeed_series([ function(x, cb) { expect(x.project_log.length).toEqual(1); // 1 because of one added in previous test above // insert object in projects[1] return db.user_query({ account_id: accounts[0], query: { project_log: obj1 }, cb: cb }); }, function(x, cb) { // and see it appear expect(x).toEqual({ action: 'insert', new_val: obj1 }); // insert object in projects[2], which has nothing to do with accounts[0] return db.user_query({ account_id: accounts[1], query: { project_log: obj2 }, cb: function(err) { if (err) { return cb(err); } else { // close the changefeed return db.user_query_cancel_changefeed({ id: changefeed_id, cb: cb }); } } }); }, function(x, cb) { // check that the next thing we get is close, not the insert we just made above. expect(x).toEqual({ action: 'close' }); return cb(); } ], done) }); }); }); describe('test time-constrained changefeed on project_log', function() { var accounts, projects; before(setup); after(teardown); accounts = []; projects = []; it('create account and project', function(done) { return async.series([ function(cb) { return create_accounts(1, function(err, x) { accounts = x; return cb(); }); }, function(cb) { return create_projects(1, accounts[0], function(err, x) { projects.push(...x); return cb(err); }); } ], done); }); return it("creates changefeed with time constraint by making two entries, one satisfying the constraint, and one not", function(done) { var changefeed_id, obj0, obj1, obj2; changefeed_id = misc.uuid(); obj0 = { id: misc.uuid(), project_id: projects[0], time: new Date(), event: { foo: 'bar0' } }; obj1 = { id: misc.uuid(), project_id: projects[0], time: misc.days_ago(2), event: { foo: 'bar1' } }; obj2 = { id: misc.uuid(), project_id: projects[0], time: new Date(), event: { foo: 'bar2' } }; return db.user_query({ account_id: accounts[0], query: { project_log: [ { id: null, project_id: null, time: { '>=': misc.days_ago(1) }, event: null } ] }, changes: changefeed_id, cb: changefeed_series([ function(x, cb) { expect(x.project_log.length).toEqual(0); // insert object that satisfies time constraint return db.user_query({ account_id: accounts[0], query: { project_log: obj0 }, cb: cb }); }, function(x, cb) { // and see it appear expect(x).toEqual({ action: 'insert', new_val: obj0 }); // insert old object that does not satisfy time constraint return db.user_query({ account_id: accounts[0], query: { project_log: obj1 }, cb: (err) => { if (err) { return cb(err); } else { // then one that does satisfy constraint return db.user_query({ account_id: accounts[0], query: { project_log: obj2 }, cb: cb }); } } }); }, function(x, cb) { var obj3; // see that *only* the new one appears expect(x).toEqual({ action: 'insert', new_val: obj2 }); // modify the first obj so its timestamp is old, and see it gets updated to be removed (so only old_val is set) obj3 = misc.deep_copy(obj0); obj3.time = misc.days_ago(60); return db.user_query({ account_id: accounts[0], query: { project_log: obj3 }, cb: cb }); }, function(x, cb) { expect(x).toEqual({ action: "delete", old_val: misc.copy_without(obj0, 'event') }); return cb(); } ], done) }); }); }); }).call(this); //# sourceMappingURL=postgres-user-queries-changefeeds.js.map