UNPKG

smc-hub

Version:

CoCalc: Backend webserver component

1,343 lines (1,324 loc) 37.4 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 syncstring user and project queries 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 syncstring table from user -- ', function() { var accounts, path, projects, ss_every; this.timeout(10000); before(setup); after(teardown); accounts = projects = 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('verifies anonymous set queries are not allowed', function(done) { return db.user_query({ query: { syncstrings: { project_id: projects[0], path: path, users: accounts } }, cb: function(err) { expect(err).toEqual("FATAL: no anonymous set queries"); return done(); } }); }); it('verifies anonymous get queries are not allowed', function(done) { return db.user_query({ query: { syncstrings: { project_id: projects[0], path: path, users: null } }, cb: function(err) { expect(err).toEqual("FATAL: anonymous get queries not allowed for table 'syncstrings'"); return done(); } }); }); it('creates a syncstring entry', function(done) { return db.user_query({ account_id: accounts[0], query: { syncstrings: { project_id: projects[0], path: path, users: accounts } }, cb: done }); }); it('verifies that entry has the documented string_id', function(done) { return db.user_query({ account_id: accounts[0], query: { syncstrings: { project_id: projects[0], path: path, users: null, string_id: null } }, cb: function(err, result) { var string_id; string_id = db.sha1(projects[0], path); expect(result != null ? result.syncstrings : void 0).toEqual({ project_id: projects[0], path: path, users: accounts, string_id: string_id }); return done(err); } }); }); it("verifies that account1 can't write to project it isn't on", function(done) { return db.user_query({ account_id: accounts[1], query: { syncstrings: { project_id: projects[0], path: 'b.txt' } }, cb: function(err) { expect(err).toEqual('FATAL: user must be an admin'); return done(); } }); }); it('makes account1 an admin', function(done) { return db.make_user_admin({ account_id: accounts[1], cb: done }); }); it("verifies that account1 as admin *can* write to project it isn't on", function(done) { return db.user_query({ account_id: accounts[1], query: { syncstrings: { project_id: projects[0], path: 'b.txt' } }, cb: done }); }); ss_every = void 0; it('writes a syncstring with every field set', function(done) { ss_every = { project_id: projects[1], path: path, users: accounts, last_snapshot: misc.hours_ago(5), snapshot_interval: 100, deleted: true, save: { state: 'requested' }, last_active: misc.hours_ago(2), init: { time: new Date() }, read_only: true, last_file_change: misc.hours_ago(3) }; return db.user_query({ account_id: accounts[0], query: { syncstrings: ss_every }, cb: done }); }); it('reads back syncstring with every field set', function(done) { var k, t; t = misc.copy(ss_every); for (k in t) { if (k === 'project_id' || k === 'path') { continue; } t[k] = null; } return db.user_query({ account_id: accounts[0], query: { syncstrings: t }, cb: function(err, result) { ss_every.string_id = db.sha1(projects[1], path); expect(result != null ? result.syncstrings : void 0).toEqual(ss_every); return done(err); } }); }); it('modifies a field of the syncstring we just created', function(done) { return db.user_query({ account_id: accounts[0], query: { syncstrings: { project_id: projects[1], path: path, read_only: false } }, cb: done }); }); it('verifies the modification', function(done) { return db.user_query({ account_id: accounts[0], query: { syncstrings: { project_id: projects[1], path: path, read_only: null } }, cb: function(err, result) { expect(result != null ? result.syncstrings : void 0).toEqual({ project_id: projects[1], path: path, read_only: false, string_id: db.sha1(projects[1], path) }); return done(err); } }); }); it('confirms that project_id must be given in set query', function(done) { return db.user_query({ account_id: accounts[0], query: { syncstrings: { path: path, read_only: true } }, cb: function(err) { expect(err).toEqual("FATAL: project_id (='undefined') must be a valid uuid"); return done(); } }); }); it('confirms that path does NOT have to be given (this would be the project-wide syncstring)', function(done) { return db.user_query({ account_id: accounts[0], query: { syncstrings: { project_id: projects[1], read_only: true } }, cb: done }); }); it('confirms that project_id must be given in get query', function(done) { return db.user_query({ account_id: accounts[0], query: { syncstrings: { path: path, read_only: null } }, cb: function(err) { expect(err).toEqual("FATAL: project_id (='undefined') must be a valid uuid"); return done(); } }); }); it('confirms that path does NOT have to be given in get query either', function(done) { return db.user_query({ account_id: accounts[0], query: { syncstrings: { project_id: projects[1], read_only: null } }, cb: done }); }); it('check that there are two syncstrings in second project', function(done) { return db._count({ table: 'syncstrings', where: { project_id: projects[1] }, cb: function(err, n) { expect(n).toEqual(2); return done(err); } }); }); return it('check two syncstring in first project', function(done) { return db._count({ table: 'syncstrings', where: { project_id: projects[0] }, cb: function(err, n) { expect(n).toEqual(2); return done(err); } }); }); }); describe('syncstring changefeed from account -- ', function() { var accounts, changefeed_id, path, projects; before(setup); after(teardown); accounts = projects = 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); }); }); changefeed_id = misc.uuid(); return it('creates a changefeed', function(done) { var obj; obj = { project_id: projects[0], path: path, read_only: true, users: accounts }; return db.user_query({ account_id: accounts[0], query: { syncstrings: [ { project_id: projects[0], path: path, read_only: null, users: null } ] }, changes: changefeed_id, cb: changefeed_series([ function(x, cb) { expect(x.syncstrings.length).toEqual(0); // create an entry matching the condition return db.user_query({ account_id: accounts[0], query: { syncstrings: obj }, cb: cb }); }, function(x, cb) { obj.string_id = db.sha1(projects[0], path); expect(x).toEqual({ action: 'insert', new_val: obj }); // modify the read_only field obj.read_only = false; return db.user_query({ account_id: accounts[0], query: { syncstrings: obj }, cb: cb }); }, function(x, cb) { expect(x.action).toEqual('update'); expect(x.new_val.read_only).toEqual(false); // modify the users field obj.users = [accounts[0]]; return db.user_query({ account_id: accounts[0], query: { syncstrings: obj }, cb: cb }); }, function(x, cb) { expect(x.action).toEqual('update'); expect(x.new_val.users).toEqual(obj.users); // change an irrelevant field and get no update, then change the read_only field back so we see something return db.user_query({ account_id: accounts[0], query: { syncstrings: { string_id: obj.string_id, project_id: obj.project_id, path: obj.path, last_active: new Date() } }, cb: function(err) { if (err) { return cb(err); } else { delete obj.last_active; obj.read_only = true; return db.user_query({ account_id: accounts[0], query: { syncstrings: obj }, cb: cb }); } } }); }, function(x, cb) { expect(x.action).toEqual('update'); expect(x.new_val.read_only).toEqual(true); return db.user_query_cancel_changefeed({ id: changefeed_id, cb: cb }); }, function(x, cb) { expect(x).toEqual({ action: 'close' }); return cb(); } ], done) }); }); }); describe('basic use of syncstring table from project -- ', function() { var accounts, path, projects, ss_every; before(setup); after(teardown); accounts = projects = 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 entry', function(done) { return db.user_query({ project_id: projects[0], query: { syncstrings: { project_id: projects[0], path: path, users: accounts } }, cb: done }); }); it('verifies that entry has the documented string_id', function(done) { return db.user_query({ project_id: projects[0], query: { syncstrings: { project_id: projects[0], path: path, users: null, string_id: null } }, cb: function(err, result) { var string_id; string_id = db.sha1(projects[0], path); expect(result != null ? result.syncstrings : void 0).toEqual({ project_id: projects[0], path: path, users: accounts, string_id: string_id }); return done(err); } }); }); it("verifies that project1 can't write to syncstring for other project", function(done) { return db.user_query({ project_id: projects[1], query: { syncstrings: { project_id: projects[0], path: 'b.txt' } }, cb: function(err) { expect(err).toEqual('FATAL: projects can only access their own syncstrings'); return done(); } }); }); ss_every = void 0; it('project1 writes a syncstring with every field set', function(done) { ss_every = { project_id: projects[1], path: path, users: accounts, last_snapshot: misc.hours_ago(5), snapshot_interval: 100, deleted: true, save: { state: 'requested' }, last_active: misc.hours_ago(2), init: { time: new Date() }, read_only: true, last_file_change: misc.hours_ago(3) }; return db.user_query({ project_id: projects[1], query: { syncstrings: ss_every }, cb: done }); }); it('reads back syncstring with every field set', function(done) { var k, t; t = misc.copy(ss_every); for (k in t) { if (k === 'project_id' || k === 'path') { continue; } t[k] = null; } return db.user_query({ project_id: projects[1], query: { syncstrings: t }, cb: function(err, result) { ss_every.string_id = db.sha1(projects[1], path); expect(result != null ? result.syncstrings : void 0).toEqual(ss_every); return done(err); } }); }); it('modifies a field of the syncstring we just created', function(done) { return db.user_query({ project_id: projects[1], query: { syncstrings: { project_id: projects[1], path: path, read_only: false } }, cb: done }); }); it('verifies the modification', function(done) { return db.user_query({ project_id: projects[1], query: { syncstrings: { project_id: projects[1], path: path, read_only: null } }, cb: function(err, result) { expect(result != null ? result.syncstrings : void 0).toEqual({ project_id: projects[1], path: path, read_only: false, string_id: db.sha1(projects[1], path) }); return done(err); } }); }); it('confirms that project_id must be given in set query', function(done) { return db.user_query({ project_id: projects[1], query: { syncstrings: { path: path, read_only: true } }, cb: function(err) { expect(err).toEqual("FATAL: project_id (='undefined') must be a valid uuid"); return done(); } }); }); it('confirms that path does NOT have to be given (this would be the project-wide syncstring)', function(done) { return db.user_query({ project_id: projects[1], query: { syncstrings: { project_id: projects[1], read_only: true } }, cb: done }); }); it('confirms that project_id must be given in get query', function(done) { return db.user_query({ project_id: projects[1], query: { syncstrings: { path: path, read_only: null } }, cb: function(err) { expect(err).toEqual("FATAL: project_id (='undefined') must be a valid uuid"); return done(); } }); }); return it('confirms that path does NOT have to be given in get query either', function(done) { return db.user_query({ project_id: projects[1], query: { syncstrings: { project_id: projects[1], read_only: null } }, cb: done }); }); }); describe('syncstring changefeed from project -- ', function() { var accounts, changefeed_id, path, projects; before(setup); after(teardown); accounts = projects = 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); }); }); changefeed_id = misc.uuid(); return it('creates a changefeed', function(done) { var obj; obj = { project_id: projects[0], path: path, read_only: true, users: accounts }; return db.user_query({ project_id: projects[0], query: { syncstrings: [ { project_id: projects[0], path: path, read_only: null, users: null } ] }, changes: changefeed_id, cb: changefeed_series([ function(x, cb) { expect(x.syncstrings.length).toEqual(0); // create an entry matching the condition return db.user_query({ project_id: projects[0], query: { syncstrings: obj }, cb: cb }); }, function(x, cb) { obj.string_id = db.sha1(projects[0], path); expect(x).toEqual({ action: 'insert', new_val: obj }); // modify the read_only field (as user not project...) obj.read_only = false; return db.user_query({ account_id: accounts[0], query: { syncstrings: obj }, cb: cb }); }, function(x, cb) { expect(x.action).toEqual('update'); expect(x.new_val.read_only).toEqual(false); // modify the users field obj.users = [accounts[0]]; return db.user_query({ project_id: projects[0], query: { syncstrings: obj }, cb: cb }); }, function(x, cb) { expect(x.action).toEqual('update'); expect(x.new_val.users).toEqual([accounts[0]]); // change an irrelevant field and get no update, then change the read_only field back so we see something return db.user_query({ project_id: projects[0], query: { syncstrings: { string_id: obj.string_id, project_id: obj.project_id, path: obj.path, last_active: new Date() } }, cb: function(err) { if (err) { return cb(err); } else { delete obj.last_active; obj.read_only = true; return db.user_query({ project_id: projects[0], query: { syncstrings: obj }, cb: cb }); } } }); }, function(x, cb) { expect(x.action).toEqual('update'); expect(x.new_val.read_only).toEqual(true); return db.user_query_cancel_changefeed({ id: changefeed_id, cb: cb }); }, function(x, cb) { expect(x).toEqual({ action: 'close' }); return cb(); } ], done) }); }); }); describe('test syncstrings_delete -- ', function() { var accounts, path, projects; before(setup); after(teardown); accounts = projects = void 0; path = 'a.txt'; it('creates 1 accounts', function(done) { return create_accounts(1, 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 entry', function(done) { return db.user_query({ account_id: accounts[0], query: { syncstrings: { project_id: projects[0], path: path, users: accounts } }, cb: done }); }); it('confirms syncstring was properly written', function(done) { return db.user_query({ account_id: accounts[0], query: { syncstrings: { project_id: projects[0], path: path, users: null } }, cb: function(err, result) { expect(result).toEqual({ syncstrings: { project_id: projects[0], path: path, users: accounts, string_id: db.sha1(projects[0], path) } }); return done(err); } }); }); it("verifies that account can't delete (since not admin)", function(done) { return db.user_query({ account_id: accounts[0], query: { syncstrings_delete: { project_id: projects[0], path: path } }, cb: function(err) { expect(err).toEqual('FATAL: user must be an admin'); return done(); } }); }); it('makes account an admin', function(done) { return db.make_user_admin({ account_id: accounts[0], cb: done }); }); it('verifies that admin can delete', function(done) { return db.user_query({ account_id: accounts[0], query: { syncstrings_delete: { project_id: projects[0], path: path } }, cb: done }); }); return it('confirms syncstring was deleted', function(done) { return db.user_query({ account_id: accounts[0], query: { syncstrings: { project_id: projects[0], path: path, users: null } }, cb: function(err, result) { expect(result).toEqual({ syncstrings: void 0 }); return done(err); } }); }); }); describe('test access roles for recent_syncstrings_in_project', function() { var accounts, path, projects; before(setup); after(teardown); accounts = projects = 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('verifies anonymous set queries are not allowed', function(done) { return db.user_query({ query: { recent_syncstrings_in_project: { project_id: projects[0], path: 'foo.txt' } }, cb: function(err) { expect(err).toEqual("FATAL: no anonymous set queries"); return done(); } }); }); it('verifies anonymous get queries are not allowed', function(done) { return db.user_query({ query: { recent_syncstrings_in_project: { project_id: projects[0], max_age_m: 15, string_id: null } }, cb: function(err) { expect(err).toEqual("FATAL: anonymous get queries not allowed for table 'recent_syncstrings_in_project'"); return done(); } }); }); it('account do a valid get query and confirms no recent syncstrings', function(done) { return db.user_query({ account_id: accounts[0], query: { recent_syncstrings_in_project: { project_id: projects[0], max_age_m: 15, string_id: null } }, cb: function(err, result) { expect(result).toEqual({ recent_syncstrings_in_project: void 0 }); return done(err); } }); }); it('project does a valid get query and confirms no recent syncstrings', function(done) { return db.user_query({ project_id: projects[0], query: { recent_syncstrings_in_project: { project_id: projects[0], max_age_m: 15, string_id: null } }, cb: function(err, result) { expect(result).toEqual({ recent_syncstrings_in_project: void 0 }); return done(err); } }); }); it('project does an invalid get query and confirms get error', function(done) { return db.user_query({ project_id: projects[1], query: { recent_syncstrings_in_project: { project_id: projects[0], max_age_m: 15, string_id: null } }, cb: function(err, result) { expect(err).toEqual('FATAL: projects can only access their own syncstrings'); return done(); } }); }); it('account do invalid get query and error', function(done) { return db.user_query({ account_id: accounts[1], query: { recent_syncstrings_in_project: { project_id: projects[0], max_age_m: 15, string_id: null } }, cb: function(err, result) { expect(err).toEqual('FATAL: user must be an admin'); return done(); } }); }); it('makes account1 an admin', function(done) { return db.make_user_admin({ account_id: accounts[1], cb: done }); }); return it('admin does previously disallowed get query and it works', function(done) { return db.user_query({ account_id: accounts[1], query: { recent_syncstrings_in_project: { project_id: projects[0], max_age_m: 15, string_id: null } }, cb: function(err, result) { expect(result).toEqual({ recent_syncstrings_in_project: void 0 }); return done(err); } }); }); }); describe('test writing and reading for recent_syncstrings_in_project -- ', function() { var accounts, changefeed_id, path0, path1, projects, string_id0, string_id1, time0, time1, time2, time3; before(setup); after(teardown); accounts = projects = void 0; it('creates 2 accounts', function(done) { return create_accounts(2, function(err, x) { accounts = x; return done(err); }); }); path0 = '1.txt'; path1 = '2.txt'; time0 = misc.minutes_ago(10); time1 = misc.minutes_ago(20); string_id0 = string_id1 = void 0; it('creates 2 projects', function(done) { return create_projects(2, accounts[0], function(err, x) { projects = x; string_id0 = db.sha1(projects[0], path0); string_id1 = db.sha1(projects[0], path1); return done(err); }); }); it('creates a syncstring entry', function(done) { return db.user_query({ account_id: accounts[0], query: { syncstrings: { project_id: projects[0], path: path0, users: accounts, last_active: time0 } }, cb: done }); }); it('creates an older syncstring entry', function(done) { return db.user_query({ account_id: accounts[0], query: { syncstrings: { project_id: projects[0], path: path1, users: accounts, last_active: time1 } }, cb: done }); }); it('as user, queries for recent syncstrings and gets it', function(done) { return db.user_query({ account_id: accounts[0], query: { recent_syncstrings_in_project: [ { project_id: projects[0], max_age_m: 15, last_active: null, string_id: null } ] }, cb: function(err, result) { expect(result).toEqual({ recent_syncstrings_in_project: [ { project_id: projects[0], last_active: time0, string_id: string_id0 } ] }); return done(err); } }); }); it('as project, queries for recent syncstrings and gets it', function(done) { return db.user_query({ project_id: projects[0], query: { recent_syncstrings_in_project: [ { project_id: projects[0], max_age_m: 15, last_active: null, string_id: null } ] }, cb: function(err, result) { expect(result).toEqual({ recent_syncstrings_in_project: [ { project_id: projects[0], last_active: time0, string_id: string_id0 } ] }); return done(err); } }); }); it('query for older syncstrings', function(done) { return db.user_query({ project_id: projects[0], query: { recent_syncstrings_in_project: [ { project_id: projects[0], max_age_m: 30, last_active: null, string_id: null } ] }, cb: function(err, result) { expect(result).toEqual({ recent_syncstrings_in_project: [ { project_id: projects[0], last_active: time0, string_id: string_id0 }, { project_id: projects[0], last_active: time1, string_id: string_id1 } ] }); return done(err); } }); }); it('ensure other project syncstrings are separate', function(done) { return db.user_query({ account_id: accounts[0], query: { recent_syncstrings_in_project: [ { project_id: projects[1], max_age_m: 30, last_active: null, string_id: null } ] }, cb: function(err, result) { expect(result).toEqual({ recent_syncstrings_in_project: [] }); return done(err); } }); }); changefeed_id = misc.uuid(); time2 = new Date(); time3 = new Date(); return it('creates and works with a changefeed', function(done) { var obj0; obj0 = void 0; return db.user_query({ project_id: projects[0], query: { recent_syncstrings_in_project: [ { project_id: projects[0], max_age_m: 15, last_active: null, string_id: null, deleted: null } ] }, changes: changefeed_id, cb: changefeed_series([ function(x, cb) { expect(x.recent_syncstrings_in_project.length).toEqual(1); obj0 = x.recent_syncstrings_in_project[0]; // change time of syncstring return db.user_query({ account_id: accounts[0], query: { syncstrings: { project_id: projects[0], path: path0, last_active: time2 } }, cb: cb }); }, function(x, cb) { obj0.last_active = time2; expect(x).toEqual({ action: 'insert', new_val: obj0 }); // change time introducing a syncstring that was old return db.user_query({ project_id: projects[0], query: { syncstrings: { project_id: projects[0], path: path1, last_active: time3 } }, cb: cb }); }, function(x, cb) { expect(x).toEqual({ action: 'insert', new_val: { last_active: time3, project_id: projects[0], string_id: string_id1 } }); // create new syncstring return db.user_query({ project_id: projects[0], query: { syncstrings: { project_id: projects[0], path: 'xyz', last_active: time3 } }, cb: cb }); }, function(x, cb) { expect(x).toEqual({ action: 'insert', new_val: { last_active: time3, project_id: projects[0], string_id: db.sha1(projects[0], 'xyz') } }); // make obj0 have old time and see get deleted return db.user_query({ account_id: accounts[0], query: { syncstrings: { project_id: projects[0], path: path0, last_active: time1 } }, cb: cb }); }, function(x, cb) { expect(x.action).toEqual('delete'); expect(x.old_val.project_id).toEqual(projects[0]); expect(x.old_val.string_id).toEqual(string_id0); // the last_active time might jitter a bit. checking if inside a bracket... time3.setSeconds(time3.getSeconds() - 1); expect(x.old_val.last_active.getTime()).toBeGreaterThan(time3.getTime()); time3.setSeconds(time3.getSeconds() + 2); expect(x.old_val.last_active.getTime()).toBeLessThan(time3.getTime()); 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-syncstring.js.map