smc-hub
Version:
CoCalc: Backend webserver component
371 lines (319 loc) • 15.9 kB
text/coffeescript
#########################################################################
# 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
###
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 patches table from user -- ', ->
@timeout(10000)
before(setup)
after(teardown)
accounts = projects = string_id = 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', (done) ->
string_id = db.sha1(projects[0], path)
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', (done) ->
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', (done) ->
db.user_query
account_id : accounts[0]
query : {patches:{string_id:string_id, time:t0, user_id:null, patch:null}}
cb : (err, x) ->
expect(x).toEqual(patches:{string_id:string_id, time:t0, user_id:0, patch:patch0})
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', (done) ->
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', (done) ->
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 : (err, x) ->
expect(x).toEqual(patches:{string_id:string_id, time:t3, user_id:1, patch:patch0, snapshot:'foo', prev:t1, sent:t2})
done(err)
it 'reads all patches so far', (done) ->
db.user_query
account_id : accounts[0]
query : {patches:[{string_id:string_id, time:null, user_id:null, patch:null}]}
cb : (err, x) ->
expect(x.patches.length).toEqual(2)
done(err)
it 'reads only the more recent patch', (done) ->
db.user_query
account_id : accounts[0]
query : {patches:[{string_id:string_id, time:{'>=':t0}, user_id:null, patch:null}]}
cb : (err, x) ->
expect(x.patches.length).toEqual(1)
expect(x.patches[0].time).toEqual(t0)
done(err)
it 'reads only the older patch', (done) ->
db.user_query
account_id : accounts[0]
query : {patches:[{string_id:string_id, time:{'<':t0}, user_id:null, patch:null}]}
cb : (err, x) ->
expect(x.patches.length).toEqual(1)
expect(x.patches[0].time).toEqual(t3)
done(err)
describe 'access control tests on patches table -- ', ->
before(setup)
after(teardown)
# SETUP
accounts = projects = string_id = undefined
path = 'a.txt'
it 'creates 3 accounts', (done) ->
create_accounts 3, (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', (done) ->
string_id = db.sha1(projects[0], path)
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', (done) ->
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', (done) ->
db.user_query
query : {patches:{string_id:null, time:null, user_id:null, patch:null}}
cb : (err) ->
expect(err).toEqual("FATAL: anonymous get queries not allowed for table 'patches'")
done()
it 'tries to write as anon to patches table and fails', (done) ->
db.user_query
query : {patches:{string_id:string_id, time:new Date(), user_id:0, patch:patch0}}
cb : (err) ->
expect(err).toEqual("FATAL: no anonymous set queries")
done()
it 'tries to write as user not on the project and fails', (done) ->
db.user_query
account_id : accounts[1]
query : {patches:{string_id:string_id, time:new Date(), user_id:0, patch:patch0}}
cb : (err) ->
expect(err).toEqual("FATAL: user must be an admin")
done()
it 'tries to write as different project and fails', (done) ->
db.user_query
project_id : projects[1]
query : {patches:{string_id:string_id, time:new Date(), user_id:0, patch:patch0}}
cb : (err) ->
expect(err).toEqual("FATAL: project not allowed to write to syncstring in different project")
done()
it 'makes account1 an admin', (done) ->
db.make_user_admin(account_id:accounts[1], cb:done)
it 'tries to write as admin and succeeds', (done) ->
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', (done) ->
db.add_user_to_project(project_id:projects[0], account_id:accounts[2], cb:done)
it 'tries to write as collab and succeeds', (done) ->
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', (done) ->
db.user_query
project_id : projects[0]
query : {patches:{string_id:string_id, time:misc.minutes_ago(1), user_id:0, patch:patch0}}
cb : (err) ->
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', (done) ->
db.user_query
project_id : projects[0]
query : {patches:{string_id:'sage', time:misc.minutes_ago(4), user_id:0, patch:patch0}}
cb : (err) ->
expect(err).toEqual("FATAL: string_id (='sage') must be a string of length 40")
done()
it 'tries to write invalid time and fails', (done) ->
db.user_query
project_id : projects[0]
query : {patches:{string_id:string_id, time:'sage', user_id:0, patch:patch0}}
cb : (err) ->
expect(err).toContain('invalid input syntax for type timestamp')
done()
it 'tries to write invalid sent type and fails', (done) ->
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 : (err) ->
expect(err).toContain('invalid input syntax for type timestamp')
done()
it 'tries to write invalid prev type and fails', (done) ->
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 : (err) ->
expect(err).toContain('invalid input syntax for type timestamp')
done()
it 'tries to change past author and fails', (done) ->
db.user_query
account_id : accounts[1]
query : {patches:{string_id:string_id, time:t0, user_id:1, patch:patch0}}
cb : (err) ->
expect(err).toEqual('you may not change the author of a patch from 0 to 1')
done()
it 'tries to write without including time field at all (and fails)', (done) ->
db.user_query
account_id : accounts[1]
query : {patches:{string_id:string_id, user_id:1, patch:patch0}}
cb : (err) ->
expect("#{err}").toEqual("FATAL: query must specify (primary) key 'time'")
done()
it 'tries to write without including string field at all (and fails)', (done) ->
db.user_query
account_id : accounts[1]
query : {patches:{time:t0, user_id:1, patch:patch0}}
cb : (err) ->
expect(err).toEqual("FATAL: string_id (='undefined') must be a string of length 40")
done()
describe 'changefeed tests on patches table', ->
before(setup)
after(teardown)
accounts = projects = string_id = undefined
path = 'a.txt'
t = (misc.minutes_ago(10-i) for i in [0...10])
patch0 = misc.to_json({a:'patch'})
it 'creates 2 accounts', (done) ->
create_accounts 2, (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', (done) ->
string_id = db.sha1(projects[0], path)
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', (done) ->
changefeed_id = misc.uuid()
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([
(x, cb) ->
expect(x?.patches?.length).toEqual(0)
# insert a new patch
db.user_query
account_id : accounts[0]
query : {patches:{string_id:string_id, time:t[0], user_id:0, patch:patch0}}
cb : cb
(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
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 : (err) ->
if err
cb(err)
else
# insert new patch
db.user_query
account_id : accounts[0]
query : {patches:{string_id:string_id, time:t[2], user_id:0, patch:'foo'}}
cb : cb
(x, cb) ->
expect(x).toEqual({action:'insert', new_val:{string_id:string_id, time:t[2], user_id:0, patch:'foo'}})
db.user_query_cancel_changefeed(id:changefeed_id, cb:cb)
(x, cb) ->
expect(x).toEqual({action:'close'})
cb()
], done)
it 'creates a changefeed as project', (done) ->
changefeed_id = misc.uuid()
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([
(x, cb) ->
expect(x?.patches?.length).toEqual(1)
# insert a new enough patch to notice
db.user_query
account_id : accounts[0]
query : {patches:{string_id:string_id, time:t[3], user_id:1, patch:patch0}}
cb : cb
(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
db.user_query
account_id : accounts[0]
query : {patches:{string_id:string_id, time:t[3], user_id:1, sent:t[1]}}
cb : cb
(x, cb) ->
expect(x.action).toEqual('update');
# Note: patch and user_id is NOT sent again since that is redundant.
expect(x.new_val).toEqual({string_id:string_id, time:t[3], sent:t[1]});
# deletes an older patch -- shouldn't fire changefeed
db._query
query : "DELETE FROM patches"
where : {string_id:string_id, time:t[0]}
cb : (err) ->
if err
cb(err)
else
# delete newer patch -- should fire changefeed
db._query
query : "DELETE FROM patches"
where : {string_id:string_id, time:t[3]}
cb : cb
(x, cb) ->
expect(x).toEqual({action:'delete', old_val:{string_id:string_id, time:t[3]}})
db.user_query_cancel_changefeed(id:changefeed_id, cb:cb)
(x, cb) ->
expect(x).toEqual({action:'close'})
cb()
], done)