smc-hub
Version:
CoCalc: Backend webserver component
968 lines (949 loc) • 25.6 kB
JavaScript
// 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