smc-hub
Version:
CoCalc: Backend webserver component
812 lines (795 loc) • 21.9 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 patches table
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('basic use of patches table from user -- ', function() {
var accounts, patch0, path, projects, string_id, t0, t1, t2, t3;
this.timeout(10000);
before(setup);
after(teardown);
accounts = projects = string_id = void 0;
path = 'a.txt';
it('creates 2 accounts', function(done) {
return create_accounts(2, function(err, x) {
accounts = x;
return done(err);
});
});
it('creates 2 projects', function(done) {
return create_projects(2, accounts[0], function(err, x) {
projects = x;
return done(err);
});
});
it('creates a syncstring', function(done) {
string_id = db.sha1(projects[0], path);
return db.user_query({
account_id: accounts[0],
query: {
syncstrings: {
project_id: projects[0],
path: path,
users: accounts
}
},
cb: done
});
});
t0 = misc.minutes_ago(10);
patch0 = misc.to_json({
a: 'patch'
});
it('user creates a patch', function(done) {
return db.user_query({
account_id: accounts[0],
query: {
patches: {
string_id: string_id,
time: t0,
user_id: 0,
patch: patch0
}
},
cb: done
});
});
it('reads the patch back', function(done) {
return db.user_query({
account_id: accounts[0],
query: {
patches: {
string_id: string_id,
time: t0,
user_id: null,
patch: null
}
},
cb: function(err, x) {
expect(x).toEqual({
patches: {
string_id: string_id,
time: t0,
user_id: 0,
patch: patch0
}
});
return done(err);
}
});
});
t1 = misc.minutes_ago(11);
t2 = misc.minutes_ago(12);
t3 = misc.minutes_ago(20);
it('user creates a patch with all fields', function(done) {
return db.user_query({
account_id: accounts[0],
query: {
patches: {
string_id: string_id,
time: t3,
user_id: 1,
patch: patch0,
snapshot: 'foo',
prev: t1,
sent: t2
}
},
cb: done
});
});
it('reads the patch with all fields back', function(done) {
return db.user_query({
account_id: accounts[0],
query: {
patches: {
string_id: string_id,
time: t3,
user_id: 1,
patch: null,
snapshot: null,
prev: null,
sent: null
}
},
cb: function(err, x) {
expect(x).toEqual({
patches: {
string_id: string_id,
time: t3,
user_id: 1,
patch: patch0,
snapshot: 'foo',
prev: t1,
sent: t2
}
});
return done(err);
}
});
});
it('reads all patches so far', function(done) {
return db.user_query({
account_id: accounts[0],
query: {
patches: [
{
string_id: string_id,
time: null,
user_id: null,
patch: null
}
]
},
cb: function(err, x) {
expect(x.patches.length).toEqual(2);
return done(err);
}
});
});
it('reads only the more recent patch', function(done) {
return db.user_query({
account_id: accounts[0],
query: {
patches: [
{
string_id: string_id,
time: {
'>=': t0
},
user_id: null,
patch: null
}
]
},
cb: function(err, x) {
expect(x.patches.length).toEqual(1);
expect(x.patches[0].time).toEqual(t0);
return done(err);
}
});
});
return it('reads only the older patch', function(done) {
return db.user_query({
account_id: accounts[0],
query: {
patches: [
{
string_id: string_id,
time: {
'<': t0
},
user_id: null,
patch: null
}
]
},
cb: function(err, x) {
expect(x.patches.length).toEqual(1);
expect(x.patches[0].time).toEqual(t3);
return done(err);
}
});
});
});
describe('access control tests on patches table -- ', function() {
var accounts, patch0, path, projects, string_id, t0;
before(setup);
after(teardown);
// SETUP
accounts = projects = string_id = void 0;
path = 'a.txt';
it('creates 3 accounts', function(done) {
return create_accounts(3, function(err, x) {
accounts = x;
return done(err);
});
});
it('creates 2 projects', function(done) {
return create_projects(2, accounts[0], function(err, x) {
projects = x;
return done(err);
});
});
it('creates a syncstring', function(done) {
string_id = db.sha1(projects[0], path);
return db.user_query({
account_id: accounts[0],
query: {
syncstrings: {
project_id: projects[0],
path: path,
users: accounts
}
},
cb: done
});
});
t0 = misc.minutes_ago(10);
patch0 = misc.to_json({
a: 'patch'
});
it('creates a patch', function(done) {
return db.user_query({
account_id: accounts[0],
query: {
patches: {
string_id: string_id,
time: t0,
user_id: 0,
patch: patch0
}
},
cb: done
});
});
it('tries to read as anon to patches table and fails', function(done) {
return db.user_query({
query: {
patches: {
string_id: null,
time: null,
user_id: null,
patch: null
}
},
cb: function(err) {
expect(err).toEqual("FATAL: anonymous get queries not allowed for table 'patches'");
return done();
}
});
});
it('tries to write as anon to patches table and fails', function(done) {
return db.user_query({
query: {
patches: {
string_id: string_id,
time: new Date(),
user_id: 0,
patch: patch0
}
},
cb: function(err) {
expect(err).toEqual("FATAL: no anonymous set queries");
return done();
}
});
});
it('tries to write as user not on the project and fails', function(done) {
return db.user_query({
account_id: accounts[1],
query: {
patches: {
string_id: string_id,
time: new Date(),
user_id: 0,
patch: patch0
}
},
cb: function(err) {
expect(err).toEqual("FATAL: user must be an admin");
return done();
}
});
});
it('tries to write as different project and fails', function(done) {
return db.user_query({
project_id: projects[1],
query: {
patches: {
string_id: string_id,
time: new Date(),
user_id: 0,
patch: patch0
}
},
cb: function(err) {
expect(err).toEqual("FATAL: project not allowed to write to syncstring in different project");
return done();
}
});
});
it('makes account1 an admin', function(done) {
return db.make_user_admin({
account_id: accounts[1],
cb: done
});
});
it('tries to write as admin and succeeds', function(done) {
return db.user_query({
account_id: accounts[1],
query: {
patches: {
string_id: string_id,
time: misc.minutes_ago(2),
user_id: 0,
patch: patch0
}
},
cb: done
});
});
it('makes account2 a collab', function(done) {
return db.add_user_to_project({
project_id: projects[0],
account_id: accounts[2],
cb: done
});
});
it('tries to write as collab and succeeds', function(done) {
return db.user_query({
account_id: accounts[2],
query: {
patches: {
string_id: string_id,
time: misc.minutes_ago(3),
user_id: 0,
patch: patch0
}
},
cb: done
});
});
it('tries to write as same project and succeeds', function(done) {
return db.user_query({
project_id: projects[0],
query: {
patches: {
string_id: string_id,
time: misc.minutes_ago(1),
user_id: 0,
patch: patch0
}
},
cb: function(err) {
return done(err);
}
});
});
/*
* NOTE: I removed this constraint, since code handles the undefined case fine,
* and it was causing problems. We should revisit this later.
it 'tries to write negative user number and fails', (done) ->
db.user_query
project_id : projects[0]
query : {patches:{string_id:string_id, time:misc.minutes_ago(4), user_id:-1, patch:patch0}}
cb : (err) ->
expect(err).toContain('new row for relation "patches" violates check constraint')
done()
it 'tries to write without including user field at all (and fails)', (done) ->
db.user_query
account_id : accounts[1]
query : {patches:{string_id:string_id, time:t0, patch:patch0}}
cb : (err) ->
expect(err).toContain('null value in column "user_id" violates not-null constraint')
done()
*/
it('tries to write invalid string_id and fails', function(done) {
return db.user_query({
project_id: projects[0],
query: {
patches: {
string_id: 'sage',
time: misc.minutes_ago(4),
user_id: 0,
patch: patch0
}
},
cb: function(err) {
expect(err).toEqual("FATAL: string_id (='sage') must be a string of length 40");
return done();
}
});
});
it('tries to write invalid time and fails', function(done) {
return db.user_query({
project_id: projects[0],
query: {
patches: {
string_id: string_id,
time: 'sage',
user_id: 0,
patch: patch0
}
},
cb: function(err) {
expect(err).toContain('invalid input syntax for type timestamp');
return done();
}
});
});
it('tries to write invalid sent type and fails', function(done) {
return db.user_query({
project_id: projects[0],
query: {
patches: {
string_id: string_id,
time: misc.minutes_ago(4),
user_id: 0,
sent: 'sage',
patch: patch0
}
},
cb: function(err) {
expect(err).toContain('invalid input syntax for type timestamp');
return done();
}
});
});
it('tries to write invalid prev type and fails', function(done) {
return db.user_query({
project_id: projects[0],
query: {
patches: {
string_id: string_id,
time: misc.minutes_ago(4),
user_id: 0,
prev: 'sage',
patch: patch0
}
},
cb: function(err) {
expect(err).toContain('invalid input syntax for type timestamp');
return done();
}
});
});
it('tries to change past author and fails', function(done) {
return db.user_query({
account_id: accounts[1],
query: {
patches: {
string_id: string_id,
time: t0,
user_id: 1,
patch: patch0
}
},
cb: function(err) {
expect(err).toEqual('you may not change the author of a patch from 0 to 1');
return done();
}
});
});
it('tries to write without including time field at all (and fails)', function(done) {
return db.user_query({
account_id: accounts[1],
query: {
patches: {
string_id: string_id,
user_id: 1,
patch: patch0
}
},
cb: function(err) {
expect(`${err}`).toEqual("FATAL: query must specify (primary) key 'time'");
return done();
}
});
});
return it('tries to write without including string field at all (and fails)', function(done) {
return db.user_query({
account_id: accounts[1],
query: {
patches: {
time: t0,
user_id: 1,
patch: patch0
}
},
cb: function(err) {
expect(err).toEqual("FATAL: string_id (='undefined') must be a string of length 40");
return done();
}
});
});
});
describe('changefeed tests on patches table', function() {
var accounts, i, patch0, path, projects, string_id, t;
before(setup);
after(teardown);
accounts = projects = string_id = void 0;
path = 'a.txt';
t = (function() {
var j, results;
results = [];
for (i = j = 0; j < 10; i = ++j) {
results.push(misc.minutes_ago(10 - i));
}
return results;
})();
patch0 = misc.to_json({
a: 'patch'
});
it('creates 2 accounts', function(done) {
return create_accounts(2, function(err, x) {
accounts = x;
return done(err);
});
});
it('creates 1 projects', function(done) {
return create_projects(1, accounts[0], function(err, x) {
projects = x;
return done(err);
});
});
it('creates a syncstring', function(done) {
string_id = db.sha1(projects[0], path);
return db.user_query({
account_id: accounts[0],
query: {
syncstrings: {
project_id: projects[0],
path: path,
users: accounts
}
},
cb: done
});
});
it('creates a changefeed as user', function(done) {
var changefeed_id;
changefeed_id = misc.uuid();
return db.user_query({
account_id: accounts[0],
query: {
patches: [
{
string_id: string_id,
time: null,
user_id: null,
patch: null
}
]
},
changes: changefeed_id,
cb: changefeed_series([
function(x,
cb) {
var ref;
expect(x != null ? (ref = x.patches) != null ? ref.length : void 0 : void 0).toEqual(0);
// insert a new patch
return db.user_query({
account_id: accounts[0],
query: {
patches: {
string_id: string_id,
time: t[0],
user_id: 0,
patch: patch0
}
},
cb: cb
});
},
function(x,
cb) {
expect(x).toEqual({
action: 'insert',
new_val: {
string_id: string_id,
time: t[0],
user_id: 0,
patch: patch0
}
});
// modify the just-inserted patch -- should not fire anything off since sent isn't a field we're watching
return db.user_query({
account_id: accounts[0],
query: {
patches: {
string_id: string_id,
time: t[0],
user_id: 0,
patch: patch0,
sent: t[1]
}
},
cb: function(err) {
if (err) {
return cb(err);
} else {
// insert new patch
return db.user_query({
account_id: accounts[0],
query: {
patches: {
string_id: string_id,
time: t[2],
user_id: 0,
patch: 'foo'
}
},
cb: cb
});
}
}
});
},
function(x,
cb) {
expect(x).toEqual({
action: 'insert',
new_val: {
string_id: string_id,
time: t[2],
user_id: 0,
patch: 'foo'
}
});
return db.user_query_cancel_changefeed({
id: changefeed_id,
cb: cb
});
},
function(x,
cb) {
expect(x).toEqual({
action: 'close'
});
return cb();
}
], done)
});
});
return it('creates a changefeed as project', function(done) {
var changefeed_id;
changefeed_id = misc.uuid();
return db.user_query({
project_id: projects[0],
query: {
patches: [
{
string_id: string_id,
time: {
'>=': t[2]
},
user_id: null,
patch: null,
sent: null
}
]
},
changes: changefeed_id,
cb: changefeed_series([
function(x,
cb) {
var ref;
expect(x != null ? (ref = x.patches) != null ? ref.length : void 0 : void 0).toEqual(1);
// insert a new enough patch to notice
return db.user_query({
account_id: accounts[0],
query: {
patches: {
string_id: string_id,
time: t[3],
user_id: 1,
patch: patch0
}
},
cb: cb
});
},
function(x,
cb) {
expect(x).toEqual({
action: 'insert',
new_val: {
string_id: string_id,
time: t[3],
user_id: 1,
patch: patch0
}
});
// modify sent part of the just-inserted patch -- should fire since we *are* watching sent column
return db.user_query({
account_id: accounts[0],
query: {
patches: {
string_id: string_id,
time: t[3],
user_id: 1,
sent: t[1]
}
},
cb: cb
});
},
function(x,
cb) {
expect(x.action).toEqual('update');
expect(x.new_val).toEqual({
string_id: string_id,
time: t[3],
sent: t[1]
});
return db._query({
query: "DELETE FROM patches",
where: {
string_id: string_id,
time: t[0]
},
cb: function(err) {
if (err) {
return cb(err);
} else {
// delete newer patch -- should fire changefeed
return db._query({
query: "DELETE FROM patches",
where: {
string_id: string_id,
time: t[3]
},
cb: cb
});
}
}
});
},
function(x,
cb) {
expect(x).toEqual({
action: 'delete',
old_val: {
string_id: string_id,
time: t[3]
}
});
return db.user_query_cancel_changefeed({
id: changefeed_id,
cb: cb
});
},
function(x,
cb) {
expect(x).toEqual({
action: 'close'
});
return cb();
}
], done)
});
});
});
}).call(this);
//# sourceMappingURL=postgres-user-queries-patches.js.map