UNPKG

smc-hub

Version:

CoCalc: Backend webserver component

658 lines (568 loc) 28.2 kB
######################################################################### # 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 ### async = require('async') expect = require('expect') pgtest = require('./pgtest') db = undefined setup = (cb) -> (pgtest.setup (err) -> db=pgtest.db; 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 -- ', -> @timeout(10000) before(setup) after(teardown) accounts = projects = undefined path = 'a.txt' it 'creates 2 accounts', (done) -> create_accounts 2, (err, x) -> accounts=x; done(err) it 'creates 2 projects', (done) -> create_projects 2, accounts[0], (err, x) -> projects=x; done(err) it 'verifies anonymous set queries are not allowed', (done) -> db.user_query query : {syncstrings:{project_id:projects[0], path:path, users:accounts}} cb : (err) -> expect(err).toEqual("FATAL: no anonymous set queries") done() it 'verifies anonymous get queries are not allowed', (done) -> db.user_query query : {syncstrings:{project_id:projects[0], path:path, users:null}} cb : (err) -> expect(err).toEqual("FATAL: anonymous get queries not allowed for table 'syncstrings'") done() it 'creates a syncstring entry', (done) -> 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', (done) -> db.user_query account_id : accounts[0] query : {syncstrings:{project_id:projects[0], path:path, users:null, string_id:null}} cb : (err, result) -> string_id = db.sha1(projects[0], path) expect(result?.syncstrings).toEqual({project_id:projects[0], path:path, users:accounts, string_id:string_id}) done(err) it "verifies that account1 can't write to project it isn't on", (done) -> db.user_query account_id : accounts[1] query : {syncstrings:{project_id:projects[0], path:'b.txt'}} cb : (err) -> expect(err).toEqual('FATAL: user must be an admin') done() it 'makes account1 an admin', (done) -> db.make_user_admin account_id : accounts[1] cb : done it "verifies that account1 as admin *can* write to project it isn't on", (done) -> db.user_query account_id : accounts[1] query : {syncstrings:{project_id:projects[0], path:'b.txt'}} cb : done ss_every = undefined it 'writes a syncstring with every field set', (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) db.user_query account_id : accounts[0] query : {syncstrings:ss_every} cb : done it 'reads back syncstring with every field set', (done) -> t = misc.copy(ss_every) for k of t if k == 'project_id' or k == 'path' continue t[k] = null db.user_query account_id : accounts[0] query : {syncstrings:t} cb : (err, result) -> ss_every.string_id = db.sha1(projects[1], path) expect(result?.syncstrings).toEqual(ss_every) done(err) it 'modifies a field of the syncstring we just created', (done) -> db.user_query account_id : accounts[0] query : {syncstrings:{project_id:projects[1], path:path, read_only:false}} cb : done it 'verifies the modification', (done) -> db.user_query account_id : accounts[0] query : {syncstrings:{project_id:projects[1], path:path, read_only:null}} cb : (err, result) -> expect(result?.syncstrings).toEqual({project_id:projects[1], path:path, read_only:false, string_id:db.sha1(projects[1], path)}) done(err) it 'confirms that project_id must be given in set query', (done) -> db.user_query account_id : accounts[0] query : {syncstrings:{path:path, read_only:true}} cb : (err) -> expect(err).toEqual("FATAL: project_id (='undefined') must be a valid uuid") done() it 'confirms that path does NOT have to be given (this would be the project-wide syncstring)', (done) -> 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', (done) -> db.user_query account_id : accounts[0] query : {syncstrings:{path:path, read_only:null}} cb : (err) -> expect(err).toEqual("FATAL: project_id (='undefined') must be a valid uuid") done() it 'confirms that path does NOT have to be given in get query either', (done) -> 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', (done) -> db._count table : 'syncstrings' where : {project_id:projects[1]} cb : (err, n) -> expect(n).toEqual(2) done(err) it 'check two syncstring in first project', (done) -> db._count table : 'syncstrings' where : {project_id:projects[0]} cb : (err, n) -> expect(n).toEqual(2) done(err) describe 'syncstring changefeed from account -- ', -> before(setup) after(teardown) accounts = projects = undefined path = 'a.txt' it 'creates 2 accounts', (done) -> create_accounts 2, (err, x) -> accounts=x; done(err) it 'creates 2 projects', (done) -> create_projects 2, accounts[0], (err, x) -> projects=x; done(err) changefeed_id = misc.uuid() it 'creates a changefeed', (done) -> obj = {project_id:projects[0], path:path, read_only:true, users:accounts} 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([ (x, cb) -> expect(x.syncstrings.length).toEqual(0) # create an entry matching the condition db.user_query(account_id: accounts[0], query: {syncstrings: obj}, cb: cb) (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 db.user_query(account_id: accounts[0], query: {syncstrings: obj}, cb: cb) (x, cb) -> expect(x.action).toEqual('update') expect(x.new_val.read_only).toEqual(false) # modify the users field obj.users = [accounts[0]] db.user_query(account_id: accounts[0], query: {syncstrings: obj}, cb: cb) (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 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 : (err) -> if err cb(err) else delete obj.last_active obj.read_only = true db.user_query(account_id: accounts[0], query: {syncstrings: obj}, cb: cb) (x, cb) -> expect(x.action).toEqual('update') expect(x.new_val.read_only).toEqual(true) db.user_query_cancel_changefeed(id:changefeed_id, cb:cb) (x, cb) -> expect(x).toEqual({action:'close'}) cb() ], done) describe 'basic use of syncstring table from project -- ', -> before(setup) after(teardown) accounts = projects = undefined path = 'a.txt' it 'creates 2 accounts', (done) -> create_accounts 2, (err, x) -> accounts=x; done(err) it 'creates 2 projects', (done) -> create_projects 2, accounts[0], (err, x) -> projects=x; done(err) it 'creates a syncstring entry', (done) -> 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', (done) -> db.user_query project_id : projects[0] query : {syncstrings:{project_id:projects[0], path:path, users:null, string_id:null}} cb : (err, result) -> string_id = db.sha1(projects[0], path) expect(result?.syncstrings).toEqual({project_id:projects[0], path:path, users:accounts, string_id:string_id}) done(err) it "verifies that project1 can't write to syncstring for other project", (done) -> db.user_query project_id : projects[1] query : {syncstrings:{project_id:projects[0], path:'b.txt'}} cb : (err) -> expect(err).toEqual('FATAL: projects can only access their own syncstrings') done() ss_every = undefined it 'project1 writes a syncstring with every field set', (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) db.user_query project_id : projects[1] query : {syncstrings:ss_every} cb : done it 'reads back syncstring with every field set', (done) -> t = misc.copy(ss_every) for k of t if k == 'project_id' or k == 'path' continue t[k] = null db.user_query project_id : projects[1] query : {syncstrings:t} cb : (err, result) -> ss_every.string_id = db.sha1(projects[1], path) expect(result?.syncstrings).toEqual(ss_every) done(err) it 'modifies a field of the syncstring we just created', (done) -> db.user_query project_id : projects[1] query : {syncstrings:{project_id:projects[1], path:path, read_only:false}} cb : done it 'verifies the modification', (done) -> db.user_query project_id : projects[1] query : {syncstrings:{project_id:projects[1], path:path, read_only:null}} cb : (err, result) -> expect(result?.syncstrings).toEqual({project_id:projects[1], path:path, read_only:false, string_id:db.sha1(projects[1], path)}) done(err) it 'confirms that project_id must be given in set query', (done) -> db.user_query project_id : projects[1] query : {syncstrings:{path:path, read_only:true}} cb : (err) -> expect(err).toEqual("FATAL: project_id (='undefined') must be a valid uuid") done() it 'confirms that path does NOT have to be given (this would be the project-wide syncstring)', (done) -> 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', (done) -> db.user_query project_id : projects[1] query : {syncstrings:{path:path, read_only:null}} cb : (err) -> expect(err).toEqual("FATAL: project_id (='undefined') must be a valid uuid") done() it 'confirms that path does NOT have to be given in get query either', (done) -> db.user_query project_id : projects[1] query : {syncstrings:{project_id:projects[1], read_only:null}} cb : done describe 'syncstring changefeed from project -- ', -> before(setup) after(teardown) accounts = projects = undefined path = 'a.txt' it 'creates 2 accounts', (done) -> create_accounts 2, (err, x) -> accounts=x; done(err) it 'creates 2 projects', (done) -> create_projects 2, accounts[0], (err, x) -> projects=x; done(err) changefeed_id = misc.uuid() it 'creates a changefeed', (done) -> obj = {project_id:projects[0], path:path, read_only:true, users:accounts} 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([ (x, cb) -> expect(x.syncstrings.length).toEqual(0) # create an entry matching the condition db.user_query(project_id: projects[0], query: {syncstrings: obj}, cb: cb) (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 db.user_query(account_id: accounts[0], query: {syncstrings: obj}, cb: cb) (x, cb) -> expect(x.action).toEqual('update') expect(x.new_val.read_only).toEqual(false) # modify the users field obj.users = [accounts[0]] db.user_query(project_id: projects[0], query: {syncstrings: obj}, cb: cb) (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 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 : (err) -> if err cb(err) else delete obj.last_active obj.read_only = true db.user_query(project_id: projects[0], query: {syncstrings: obj}, cb: cb) (x, cb) -> expect(x.action).toEqual('update') expect(x.new_val.read_only).toEqual(true) db.user_query_cancel_changefeed(id:changefeed_id, cb:cb) (x, cb) -> expect(x).toEqual({action:'close'}) cb() ], done) describe 'test syncstrings_delete -- ', -> before(setup) after(teardown) accounts = projects = undefined path = 'a.txt' it 'creates 1 accounts', (done) -> create_accounts 1, (err, x) -> accounts=x; done(err) it 'creates 1 projects', (done) -> create_projects 1, accounts[0], (err, x) -> projects=x; done(err) it 'creates a syncstring entry', (done) -> 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', (done) -> db.user_query account_id : accounts[0] query : {syncstrings:{project_id:projects[0], path:path, users:null}} cb : (err, result) -> expect(result).toEqual({syncstrings:{project_id:projects[0], path:path, users:accounts, string_id:db.sha1(projects[0], path)}}) done(err) it "verifies that account can't delete (since not admin)", (done) -> db.user_query account_id : accounts[0] query : {syncstrings_delete:{project_id:projects[0], path:path}} cb : (err) -> expect(err).toEqual('FATAL: user must be an admin') done() it 'makes account an admin', (done) -> db.make_user_admin(account_id: accounts[0], cb: done) it 'verifies that admin can delete', (done) -> db.user_query account_id : accounts[0] query : {syncstrings_delete:{project_id:projects[0], path:path}} cb : done it 'confirms syncstring was deleted', (done) -> db.user_query account_id : accounts[0] query : {syncstrings:{project_id:projects[0], path:path, users:null}} cb : (err, result) -> expect(result).toEqual({syncstrings:undefined}) done(err) describe 'test access roles for recent_syncstrings_in_project', -> before(setup) after(teardown) accounts = projects = undefined path = 'a.txt' it 'creates 2 accounts', (done) -> create_accounts 2, (err, x) -> accounts=x; done(err) it 'creates 2 projects', (done) -> create_projects 2, accounts[0], (err, x) -> projects=x; done(err) it 'verifies anonymous set queries are not allowed', (done) -> db.user_query query : {recent_syncstrings_in_project:{project_id:projects[0], path:'foo.txt'}} cb : (err) -> expect(err).toEqual("FATAL: no anonymous set queries") done() it 'verifies anonymous get queries are not allowed', (done) -> db.user_query query : {recent_syncstrings_in_project:{project_id:projects[0], max_age_m:15, string_id:null}} cb : (err) -> expect(err).toEqual("FATAL: anonymous get queries not allowed for table 'recent_syncstrings_in_project'") done() it 'account do a valid get query and confirms no recent syncstrings', (done) -> db.user_query account_id : accounts[0] query : {recent_syncstrings_in_project:{project_id:projects[0], max_age_m:15, string_id:null}} cb : (err, result) -> expect(result).toEqual(recent_syncstrings_in_project:undefined) done(err) it 'project does a valid get query and confirms no recent syncstrings', (done) -> db.user_query project_id : projects[0] query : {recent_syncstrings_in_project:{project_id:projects[0], max_age_m:15, string_id:null}} cb : (err, result) -> expect(result).toEqual(recent_syncstrings_in_project:undefined) done(err) it 'project does an invalid get query and confirms get error', (done) -> db.user_query project_id : projects[1] query : {recent_syncstrings_in_project:{project_id:projects[0], max_age_m:15, string_id:null}} cb : (err, result) -> expect(err).toEqual('FATAL: projects can only access their own syncstrings') done() it 'account do invalid get query and error', (done) -> db.user_query account_id : accounts[1] query : {recent_syncstrings_in_project:{project_id:projects[0], max_age_m:15, string_id:null}} cb : (err, result) -> expect(err).toEqual('FATAL: user must be an admin') done() it 'makes account1 an admin', (done) -> db.make_user_admin account_id : accounts[1] cb : done it 'admin does previously disallowed get query and it works', (done) -> db.user_query account_id : accounts[1] query : {recent_syncstrings_in_project:{project_id:projects[0], max_age_m:15, string_id:null}} cb : (err, result) -> expect(result).toEqual(recent_syncstrings_in_project:undefined) done(err) describe 'test writing and reading for recent_syncstrings_in_project -- ', -> before(setup) after(teardown) accounts = projects = undefined it 'creates 2 accounts', (done) -> create_accounts 2, (err, x) -> accounts=x; done(err) path0 = '1.txt' path1 = '2.txt' time0 = misc.minutes_ago(10) time1 = misc.minutes_ago(20) string_id0 = string_id1 = undefined it 'creates 2 projects', (done) -> create_projects 2, accounts[0], (err, x) -> projects=x string_id0 = db.sha1(projects[0], path0) string_id1 = db.sha1(projects[0], path1) done(err) it 'creates a syncstring entry', (done) -> 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', (done) -> 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', (done) -> 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 : (err, result) -> expect(result).toEqual(recent_syncstrings_in_project:[{project_id:projects[0], last_active:time0, string_id:string_id0}]) done(err) it 'as project, queries for recent syncstrings and gets it', (done) -> 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 : (err, result) -> expect(result).toEqual(recent_syncstrings_in_project:[{project_id:projects[0], last_active:time0, string_id:string_id0}]) done(err) it 'query for older syncstrings', (done) -> 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 : (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}]) done(err) it 'ensure other project syncstrings are separate', (done) -> 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 : (err, result) -> expect(result).toEqual(recent_syncstrings_in_project:[]) done(err) changefeed_id = misc.uuid() time2 = new Date() time3 = new Date() it 'creates and works with a changefeed', (done) -> obj0 = undefined 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([ (x, cb) -> expect(x.recent_syncstrings_in_project.length).toEqual(1) obj0 = x.recent_syncstrings_in_project[0] # change time of syncstring db.user_query account_id : accounts[0] query : {syncstrings:{project_id:projects[0], path:path0, last_active:time2}} cb : cb (x, cb) -> obj0.last_active = time2 expect(x).toEqual({action:'insert', new_val:obj0}) # change time introducing a syncstring that was old db.user_query project_id : projects[0] query : {syncstrings:{project_id:projects[0], path:path1, last_active:time3}} cb : cb (x, cb) -> expect(x).toEqual({action:'insert', new_val:{last_active:time3, project_id:projects[0], string_id:string_id1}}) # create new syncstring db.user_query project_id : projects[0] query : {syncstrings:{project_id:projects[0], path:'xyz', last_active:time3}} cb : cb (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 db.user_query account_id : accounts[0] query : {syncstrings:{project_id:projects[0], path:path0, last_active:time1}} cb : cb (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()) db.user_query_cancel_changefeed(id:changefeed_id, cb:cb) (x, cb) -> expect(x).toEqual({action:'close'}) cb() ], done)