#########################################################################
# 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 2 -- projects, ....?

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 'very basic test of projects table', ->
    @timeout(10000)
    before(setup)
    after(teardown)

    it 'creates account, project feed, a project, and see it appear', (done) ->
        changefeed_id = misc.uuid()
        accounts = undefined
        projects = []
        async.series([
            (cb) ->
                create_accounts 1, (err, x) -> accounts=x; cb(err)
            (cb) ->
                db.user_query
                    account_id : accounts[0]
                    query      : {projects:[{project_id:null, title:null, users:null}]}
                    changes    : changefeed_id
                    cb         : changefeed_series([
                        (x, cb) ->
                            expect(x.projects.length).toEqual(0)
                            create_projects 1, accounts[0], (err, v) ->
                                projects.push(v[0])
                                cb(err)
                        (x, cb) ->
                            expect(x).toEqual({ action: 'insert', new_val: { project_id: projects[0], title: 'Project 0', users:{"#{accounts[0]}":{group:"owner"}} } })

                            # Test removing user from the project
                            db.remove_user_from_project(account_id:accounts[0], project_id:projects[0], cb:cb)
                        (x, cb) ->
                            expect(x).toEqual({ action: 'insert', new_val: { project_id: projects[0], title: 'Project 0', users: {} } })
                            cb()
                        (x, cb) ->
                            expect(x).toEqual({ action: 'delete', old_val: { project_id: projects[0] } })

                            # Test adding user back to the project
                            db.add_user_to_project(account_id:accounts[0], project_id:projects[0], cb:cb)
                        (x, cb) ->
                            expect(x).toEqual({ action: 'insert', new_val: { project_id: projects[0], title: 'Project 0', users:{"#{accounts[0]}":{group:"collaborator"}} } })

                            # create another project
                            create_projects 1, accounts[0], (err, v) ->
                                projects.push(v[0])
                                cb(err)
                        (x, cb) ->
                            expect(x).toEqual({ action: 'insert', new_val: { project_id: projects[1], title: 'Project 0', users:{"#{accounts[0]}":{group:"owner"}} } })
                            cb()
                            # Test actually deleting project completely from database
                            db._query
                                query : "DELETE FROM projects"
                                where : {"project_id = $::UUID":projects[1]}
                                cb    : cb
                        (x, cb) ->
                            expect(x).toEqual({ action: 'delete', old_val: { project_id: projects[1] } })
                            db.user_query_cancel_changefeed(id:changefeed_id, cb:cb)
                        (x, cb) ->
                            expect(x).toEqual({action:'close'})
                            cb()
                    ], cb)
        ], done)

describe 'create multiple projects with multiple collaborators', ->
    before(setup)
    after(teardown)

    #log = console.log
    log = ->
    it 'create 3 accounts and several projects, and see them appear in one projects feed properly', (done) ->
        accounts = undefined
        projects = []
        changefeed_id = misc.uuid()
        async.series([
            (cb) ->
                create_accounts 3, (err, x) ->
                    accounts=x; cb(err)
            (cb) ->
                db.user_query
                    account_id : accounts[0]
                    query      : {projects:[{project_id:null, users:null}]}
                    changes    : changefeed_id
                    cb         : changefeed_series([
                        (x, cb) ->
                            expect(x.projects.length).toEqual(0)

                            log 'create first project'
                            create_projects 1, accounts[0], (err, v) ->
                                projects.push(v[0])
                                cb(err)
                        (x, cb) ->
                            expect(x).toEqual({ action: 'insert', new_val: { project_id: projects[0], users:{"#{accounts[0]}":{group:"owner"}} } })
                            log 'create another project'
                            create_projects 1, accounts[0], (err, v) ->
                                projects.push(v[0])
                                cb(err)
                        (x, cb) ->
                            expect(x).toEqual({ action: 'insert', new_val: { project_id: projects[1], users:{"#{accounts[0]}":{group:"owner"}} } })

                            log 'create a project that will get ignored by the feed...'
                            create_projects 1, accounts[1], (err, v) ->
                                if err
                                    cb(err); return
                                projects.push(v[0])
                                log '... until we add the first user to it, in which case....'
                                db.add_user_to_project(project_id:v[0], account_id:accounts[0], cb:cb)
                        (x, cb) ->
                            log '... it appears!'
                            expect(x).toEqual({ action: 'insert', new_val: { project_id: projects[2], users:{"#{accounts[0]}":{group:"collaborator"}, "#{accounts[1]}":{group:"owner"}} } })

                            log 'Now add another collaborator'
                            db.add_user_to_project(project_id:projects[2], account_id:accounts[2], cb:cb)
                        (x, cb) ->
                            expect(x).toEqual({ action: 'insert', new_val: { project_id: projects[2], users:{"#{accounts[0]}":{group:"collaborator"}, "#{accounts[1]}":{group:"owner"}, "#{accounts[2]}":{group:"collaborator"}} } })

                            log 'Now take first user back off'
                            db.remove_user_from_project(project_id:projects[2], account_id:accounts[0], cb:cb)
                        (x, cb) ->
                            expect(x).toEqual({ action: 'update', new_val: { project_id: projects[2], users:{"#{accounts[1]}":{group:"owner"}, "#{accounts[2]}":{group:"collaborator"}} } })
                            cb()
                        (x, cb) ->
                            expect(x).toEqual({ action: 'delete', old_val: { project_id: projects[2] }})

                            log 'cancel feed'
                            db.user_query_cancel_changefeed(id:changefeed_id, cb:cb)
                        (x, cb) ->
                            expect(x).toEqual({action:'close'})
                            cb()
                    ], cb)
            ], done)

describe 'changefeed on a single project', ->
    before(setup)
    after(teardown)

    it 'make 2 projects, feed on single, remove and add user', (done) ->
        changefeed_id = misc.uuid()
        accounts = projects = undefined
        async.series([
            (cb) ->
                create_accounts 1, (err, x) -> accounts=x; cb(err)
            (cb) ->
                # make 2 projects; one will be comletely ignored
                create_projects 2, accounts[0], (err, v) ->
                    projects = v
                    cb(err)
            (cb) ->
                db.user_query
                    account_id : accounts[0]
                    query      : {projects:[{project_id:projects[0], description:null}]}
                    changes    : changefeed_id
                    cb         : changefeed_series([
                        (x, cb) ->
                            expect(x.projects.length).toEqual(1)

                            db.remove_user_from_project(project_id:projects[0], account_id:accounts[0], cb:cb)
                        (x, cb) ->
                            expect(x).toEqual({ action: 'delete', old_val: { project_id: projects[0]} })

                            db.add_user_to_project(project_id:projects[0], account_id:accounts[0], cb:cb)
                        (x, cb) ->
                            expect(x).toEqual({ action: 'insert', new_val: { project_id: projects[0], description: "Description 0"} })
                            db.user_query_cancel_changefeed(id:changefeed_id, cb:cb)
                        (x, cb) ->
                            expect(x).toEqual({action:'close'})
                            cb()
                    ], cb)
        ], done)

describe 'changefeed testing all projects fields', ->
    before(setup)
    after(teardown)

    it 'make 2 projects, feed, and edit all fields', (done) ->
        changefeed_id = misc.uuid()
        accounts = projects = undefined
        obj0 = undefined
        last_edited = undefined
        user_query = (opts) ->
            opts.account_id = accounts[0]
            db.user_query(opts)
        async.series([
            (cb) ->
                create_accounts 1, (err, x) -> accounts=x; cb(err)
            (cb) ->
                # make 2 projects
                create_projects 2, accounts[0], (err, v) ->
                    projects = v
                    cb(err)
            (cb) ->
                user_query
                    query  : {projects:[{ project_id: null, title: null, description: null, users: null, invite: null, invite_requests:null, deleted: null, host: null, settings: null, status: null, state: null, last_edited: null, last_active: null, action_request: null, course: null}]}
                    changes: changefeed_id
                    cb     : changefeed_series([
                        (x, cb) ->
                            expect(x.projects.length).toEqual(2)
                            for p in x.projects
                                if p.project_id == projects[0]
                                    obj0 = p

                            user_query
                                query : {projects:{project_id:projects[0], title:"Foo", description:"bar"}}
                                cb    : cb
                        (x, cb) ->
                            obj0.title = 'Foo'
                            obj0.description = 'bar'
                            expect(x).toEqual( { action: 'insert', new_val: obj0 })

                            user_query
                                query : {projects:{project_id:projects[0], deleted:true}}
                                cb    : cb
                        (x, cb) ->
                            obj0.deleted = true
                            expect(x.action).toEqual('update');
                            expect(x.new_val.deleted).toEqual(obj0.deleted)

                            obj0.action_request = {action:'test', started:new Date()}
                            user_query
                                query : {projects:{project_id:projects[0], action_request:obj0.action_request}}
                                cb    : cb
                        (x, cb) ->
                            expect(x.action).toEqual('update');
                            expect(x.new_val.action_request).toEqual(obj0.action_request)

                            obj0.last_edited = new Date()
                            db._query
                                query : "UPDATE projects"
                                set   : {last_edited : obj0.last_edited}
                                where : {project_id : projects[0]}
                                cb    : cb
                        (x, cb) ->
                            expect(x.action).toEqual('update');
                            expect(x.new_val.last_edited).toEqual(obj0.last_edited)

                            set =
                                invite          : {a:'map'}
                                invite_requests : {b:'map2'}
                                host            : {host:'compute0-us'}
                                status          : {c:'map3'}
                                state           : {d:'map4'}
                                last_active     : {"#{accounts[0]}":new Date()}
                                course          : {project_id:obj0.project_id}
                            misc.merge(obj0, set)
                            db._query
                                query : "UPDATE projects"
                                set   : set
                                where : {project_id : projects[0]}
                                cb    : cb
                        (x, cb) ->
                            expect(x.action).toEqual('update')
                            for field in ['invite', 'invite_requests', 'host', 'status', 'state', 'last_active', 'course']
                                expect(x.new_val[field]).toEqual(obj0[field])

                            db.user_query_cancel_changefeed(id:changefeed_id, cb:cb)
                        (x, cb) ->
                            expect(x).toEqual({action:'close'})
                            cb()
                ], cb)
        ], done)

describe 'testing a changefeed from a project (instead of account)', ->
    before(setup)
    after(teardown)

    it 'makes a projects, has project get a feed and see changes', (done) ->
        changefeed_id = misc.uuid()
        accounts = projects = obj = undefined
        async.series([
            (cb) ->
                create_accounts 1, (err, x) -> accounts=x; cb(err)
            (cb) ->
                create_projects 1, accounts[0], ((err, v) -> projects = v; cb(err))
            (cb) ->
                db.user_query
                    project_id : projects[0]
                    query      : {projects:[{project_id:projects[0], title:null, description:null}]}
                    changes    : changefeed_id
                    cb         : changefeed_series([
                        (x, cb) ->
                            obj = { description: 'Description 0', project_id: projects[0], title: 'Project 0' }
                            expect(x.projects).toEqual([obj])

                            obj.title = 'Title'; obj.description = 'Description'
                            db.user_query
                                project_id : projects[0]
                                query      : {projects:obj}
                                cb         : cb
                        (x, cb) ->
                            expect(x).toEqual({action:'insert', new_val:obj})

                            db.user_query_cancel_changefeed(id:changefeed_id, cb:cb)
                        (x, cb) ->
                            expect(x).toEqual({action:'close'})

                            cb()
                    ], cb)
        ], done)

describe 'test changefeed admin-only access to project', ->
    before(setup)
    after(teardown)

    accounts = project_id = undefined

    it 'set things up', (done) ->
        async.series([
            (cb) ->
                create_accounts 3, (err, x) -> accounts=x; cb(err)
            (cb) ->
                db.make_user_admin(account_id: accounts[0], cb:cb)
            (cb) ->
                create_projects 1, accounts[2], ((err, v) -> project_id = v[0]; cb(err))
        ], done)

    it 'tests writing to project as admin user', (done) ->
        db.user_query
            account_id : accounts[0]
            query      : {projects:{project_id:project_id, title:"Better Title"}}
            cb         : done

    it 'tests writing to project_admin as admin user', (done) ->
        db.user_query
            account_id : accounts[0]
            query      : {projects_admin:{project_id:project_id, title:"Better Title"}}
            cb         : (err) ->
                expect(err).toEqual("FATAL: user set queries not allowed for table 'projects_admin'")
                done()

    it 'tests project title changed properly (so reading as admin)', (done) ->
        db.user_query
            account_id : accounts[0]
            query      : {projects:{project_id:project_id, title:null}}
            cb         : (err, x) ->
                expect(x).toEqual(projects:{project_id:project_id, title:"Better Title"})
                done(err)

    it 'tests writing to project as non-collab', (done) ->
        db.user_query
            account_id : accounts[1]
            query      : {projects:{project_id:project_id, title:"Even Better Title"}}
            cb         : (err) ->
                expect(err).toEqual('FATAL: user must be an admin')
                done()

    it 'tests reading from project as non-collab', (done) ->
        db.user_query
            account_id : accounts[1]
            query      : {projects:{project_id:project_id, title:null}}
            cb         : (err) ->
                expect(err).toEqual('FATAL: you do not have read access to this project')
                done()

    it 'tests writing to project as anonymous', (done) ->
        db.user_query
            query      : {projects:{project_id:project_id, title:null}}
            cb         : (err) ->
                expect(err).toEqual("FATAL: anonymous get queries not allowed for table 'projects'")
                done()

    it 'tests admin changefeed on projects_admin table', (done) ->
        changefeed_id = misc.uuid()
        db.user_query
            account_id : accounts[0]  # our admin
            query      : {projects_admin:[{project_id:project_id, title:null}]}
            changes    : changefeed_id
            cb         : changefeed_series([
                (x, cb) ->
                    expect(x.projects_admin).toEqual([{ project_id: project_id, title: 'Better Title' }])

                    db.user_query
                        account_id : accounts[0]
                        query      : {projects:{project_id:project_id, title:"WAY Better Title"}}
                        cb         : cb
                (x, cb) ->
                    expect(x).toEqual({action:'insert', new_val:{project_id:project_id, title:"WAY Better Title"}})

                    db.user_query_cancel_changefeed(id:changefeed_id, cb:cb)
                (x, cb) ->
                    expect(x).toEqual({action:'close'})

                    cb()
                ], done)

    it 'tests that FATAL: user must be an admin to read from (or get changefeed on) projects_admin table', (done) ->
        changefeed_id = misc.uuid()
        db.user_query
            account_id : accounts[1]  # NOT admin
            query      : {projects_admin:[{project_id:project_id, title:null}]}
            changes    : changefeed_id
            cb         : (err) ->
                expect(err).toEqual('FATAL: user must be an admin')
                done()


describe 'test public_projects table -- ', ->
    before(setup)
    after(teardown)

    accounts = project_id = undefined

    it 'set things up', (done) ->
        async.series([
            (cb) ->
                create_accounts 2, (err, x) -> accounts=x; cb(err)
            (cb) ->
                create_projects 1, accounts[0], ((err, v) -> project_id = v[0]; cb(err))
        ], done)

    it 'get error if project is not public, i.e., has no public paths', (done) ->
        db.user_query
            account_id : accounts[1]
            query      : {public_projects:{project_id:project_id, title:null, description:null}}
            cb         : (err, x) ->
                expect(err).toEqual("project does not have any public paths")
                done()

    it 'adds a public paths', (done) ->
        db.user_query
            account_id : accounts[0]
            query      : {public_paths:{project_id:project_id, path:"foo.txt"}}
            cb         : done

    it 'tests owner can now get title and description of project', (done) ->
        db.user_query
            account_id : accounts[0]
            query      : {public_projects:{project_id:project_id, title:null, description:null}}
            cb         : (err, x) ->
                expect(x).toEqual(public_projects:{project_id:project_id, title:'Project 0', description:'Description 0'})
                done(err)

    it 'tests other user can get title and description of project', (done) ->
        db.user_query
            account_id : accounts[1]
            query      : {public_projects:{project_id:project_id, title:null, description:null}}
            cb         : (err, x) ->
                expect(x).toEqual(public_projects:{project_id:project_id, title:'Project 0', description:'Description 0'})
                done(err)

    it 'tests anonymous user can get title and description of project', (done) ->
        db.user_query
            account_id : accounts[1]
            query      : {public_projects:{project_id:project_id, title:null, description:null}}
            cb         : (err, x) ->
                expect(x).toEqual(public_projects:{project_id:project_id, title:'Project 0', description:'Description 0'})
                done(err)


    it 'tests that project_id must be specified', (done) ->
        db.user_query
            account_id : accounts[0]
            query      : {public_projects:{project_id:null, title:null, description:null}}
            cb         : (err, x) ->
                expect(err).toEqual('FATAL: must specify project_id')
                done()

    tests = (account_id, done) ->
        id = misc.uuid()
        db.user_query
            account_id : account_id
            query      : {public_projects:[{project_id:project_id, title:null, description:null}]}
            changes    : id
            cb         : changefeed_series([
                    (x, cb) ->
                        expect(x).toEqual(public_projects:[{project_id:project_id, title:'Project 0', description:'Description 0'}])
                        db.user_query
                            account_id : accounts[0]
                            query      : {projects:{project_id:project_id, title:'TITLE', description:'DESC'}}
                            cb         : cb
                    (x, cb) ->
                        expect(x).toEqual({ action: 'insert', new_val: { project_id: project_id, description: 'DESC', title: 'TITLE' } })
                        db.user_query
                            account_id : accounts[0]
                            query      : {projects:{project_id:project_id, title:'Project 0', description:'Description 0'}}
                            cb         : cb
                    (x, cb) ->
                        db.user_query_cancel_changefeed(id:id, cb:cb)
                    (x, cb) ->
                        expect(x).toEqual({action:'close'})
                        cb()
                ], done)

    it 'tests non-anonymous user on project can get a changefeed on public project', (done) ->
        tests(accounts[0], done)

    it 'tests non-anonymous NON-user on project can get a changefeed on public project', (done) ->
        tests(accounts[1], done)

    it 'tests anonymous can get a changefeed on public project', (done) ->
        tests(undefined, done)


describe 'test public_paths table -- ', ->
    before(setup)
    after(teardown)

    accounts = projects = undefined

    it 'set things up', (done) ->
        async.series([
            (cb) ->
                create_accounts 3, (err, x) -> accounts=x; cb(err)
            (cb) ->
                db.make_user_admin(account_id: accounts[2], cb:cb)
            (cb) ->
                create_projects 2, accounts[0], ((err, v) -> projects = v; cb(err))
        ], done)

    it 'adds a public path to a project', (done) ->
        db.user_query
            account_id : accounts[0]
            query      : {public_paths:{project_id:projects[0], path:"foo.txt", description:"foo"}}
            cb         : done

    it 'adds a public path to a project not on as admin', (done) ->
        db.user_query
            account_id : accounts[2]
            query      : {public_paths:{project_id:projects[0], path:"bar.txt", description:'bar', disabled:true}}
            cb         : done

    it 'fail to add a public path to a project user is not on', (done) ->
        db.user_query
            account_id : accounts[1]
            query      : {public_paths:{project_id:projects[0], path:"bar2.txt"}}
            cb         : (err) ->
                expect(err).toEqual('FATAL: user must be an admin')
                done()

    it 'fail to add a public path when not logged in', (done) ->
        db.user_query
            query      : {public_paths:{project_id:projects[0], path:'foo2.txt'}}
            cb         : (err) ->
                expect(err).toEqual('FATAL: no anonymous set queries')
                done()

    read_public_paths = (done) ->
        f = (account_id, cb) ->
            db.user_query
                account_id : account_id
                query      : {public_paths:[{project_id:projects[0], path:null, description:null, disabled:null}]}
                options    : [{order_by:'path'}]
                cb         : (err, x) ->
                    expect(x).toEqual({ public_paths: [ { description: 'bar', disabled: true, \
                                path: 'bar.txt', project_id: projects[0] },  { description: 'foo', \
                                path: 'foo.txt', project_id: projects[0] } ] })
                    cb(err)
        async.map([accounts[0], accounts[1], undefined], f, done)

    it 'reads public paths as owner, non-collab, and anon', (done) ->
        read_public_paths(done)

    it 'verifies that changefeed required id field (the primary key)', (done) ->
        db.user_query
            query      : {public_paths:[{project_id:projects[0], path:null}]}
            changes    : misc.uuid()
            cb         : (err) =>
                expect(err).toEqual("FATAL: changefeed MUST include primary key (='id') in query")
                done()

    changefeed_pub_paths = (done) ->
        f = (account_id, cb) ->
            changefeed_id = misc.uuid()
            v = undefined
            db.user_query
                account_id : account_id
                query      : {public_paths:[{id:null, project_id:projects[0], path:null, description:null, disabled:null}]}
                options    : [order_by:'path']
                changes    : changefeed_id
                cb         : changefeed_series([
                    (x, cb) ->
                        v = x.public_paths
                        expect(v.length).toEqual(2)
                        db.user_query
                            account_id : accounts[0]
                            query      : {public_paths:{project_id:projects[0], path:"foo.txt",\
                                                        description:"foo2", disabled:true}}
                            cb         : cb
                    (x, cb) ->
                        expect(x).toEqual({ action: 'insert', new_val: {id:v[1].id, project_id:projects[0],  \
                                                                        path:"foo.txt", description:"foo2", disabled:true} })

                        db.user_query
                            account_id : accounts[0]
                            query      : {public_paths:{project_id:projects[0], path:"foo.txt",  \
                                                        description:"foo", disabled:false}}
                            cb         : cb
                    (x, cb) ->
                        expect(x.action).toEqual('update');
                        expect(x.new_val.description).toEqual("foo")
                        expect(x.new_val.disabled).toEqual(false);

                        db.user_query_cancel_changefeed(id:changefeed_id, cb:cb)
                    (x, cb) ->
                        expect(x).toEqual({action:'close'})
                        cb()
                ], cb)
        async.mapSeries([accounts[0], accounts[1], undefined], f, done)

    it 'makes a changefeed and verifies modifying existing entry works', (done) ->
        changefeed_pub_paths(done)

describe 'test site_settings table -- ', ->
    before(setup)
    after(teardown)

    accounts = undefined

    it 'make an admin and non-admin account', (done) ->
        async.series([
            (cb) ->
                create_accounts 2, (err, x) -> accounts=x; cb(err)
            (cb) ->
                db.make_user_admin(account_id: accounts[0], cb:cb)
        ], done)

    it "check writing to wrong field gives an error", (done) ->
        db.user_query
            account_id : accounts[0]
            query : {site_settings:{site_name:'Hacker Site!'}}
            cb    : (err) ->
                expect(err).toEqual("FATAL: error setting 'name' -- Error: setting name='undefined' not allowed")
                done()

    it "check writing to not allowed row", (done) ->
        db.user_query
            account_id : accounts[0]
            query : {site_settings:{name:'foobar', value:'stuff'}}
            cb    : (err) ->
                expect(err).toEqual("FATAL: error setting 'name' -- Error: setting name='foobar' not allowed")
                done()

    it "check anon can't write", (done) ->
        db.user_query
            query : {site_settings:{name:'site_name', value:'Hacker Site!'}}
            cb    : (err) ->
                expect(err).toEqual("FATAL: no anonymous set queries")
                done()

    it "check anon can't read", (done) ->
        db.user_query
            query : {site_settings:{name:'site_name', value:null}}
            cb    : (err) ->
                expect(err).toEqual("FATAL: anonymous get queries not allowed for table 'site_settings'")
                done()

    it "check non-admin can't write", (done) ->
        db.user_query
            account_id : accounts[1]
            query : {site_settings:{name:'site_name', value:'Hacker Site!'}}
            cb    : (err) ->
                expect(err).toEqual("FATAL: user must be an admin")
                done()

    it "check non-admin can't read", (done) ->
        db.user_query
            account_id : accounts[1]
            query : {site_settings:{name:'site_name', value:null}}
            cb    : (err) ->
                expect(err).toEqual("FATAL: user must be an admin")
                done()

    it "check admin can write", (done) ->
        db.user_query
            account_id : accounts[0]
            query : {site_settings:{name:'site_name', value:'Hacker Site!'}}
            cb    : done

    it "check admin can read", (done) ->
        db.user_query
            account_id : accounts[0]
            query : {site_settings:{name:'site_name', value:null}}
            cb    : (err, x) ->
                expect(x).toEqual({ site_settings: { name: 'site_name', value: 'Hacker Site!' } } )
                done()

    it 'create admin changefeed and write some things to it', (done) ->
        id = misc.uuid()
        user_query = (query, cb) ->
            db.user_query(account_id:accounts[0], query:{site_settings:query}, cb:cb)
        db.user_query
            account_id : accounts[0]
            query      : {site_settings:[{name:null, value:null}]}
            changes    : id
            cb         : changefeed_series([
                    (x, cb) ->
                        expect(x).toEqual(site_settings:[{name:'site_name', value:'Hacker Site!'}])

                        user_query({name:'site_name', value:'CoCalc'}, cb)
                    (x, cb) ->
                        expect(x).toEqual({ action: 'insert', new_val: {name:'site_name', value:'CoCalc'} })

                        user_query({name:'site_description', value:'The collaborative site'}, cb)
                    (x, cb) ->
                        expect(x).toEqual({ action: 'insert', new_val: {name:'site_description', value:'The collaborative site'} })

                        user_query({name:'terms_of_service', value:'Do nice things'}, cb)
                    (x, cb) ->
                        expect(x).toEqual({ action: 'insert', new_val: {name:'terms_of_service', value:'Do nice things'} })

                        user_query({name:'account_creation_email_instructions', value:'Create account'}, cb)
                    (x, cb) ->
                        expect(x).toEqual({ action: 'insert', new_val: {name:'account_creation_email_instructions', value:'Create account'} })
                        user_query({name:'help_email', value:'h@a.b.c'}, cb)
                    (x, cb) ->
                        expect(x).toEqual({ action: 'insert', new_val: {name:'help_email', value:'h@a.b.c'} })

                        user_query({name:'commercial', value:'yes'}, cb)
                    (x, cb) ->
                        expect(x).toEqual({ action: 'insert', new_val: {name:'commercial', value:'yes'} })

                        db.user_query_cancel_changefeed(id:id, cb:cb)
                    (x, cb) ->
                        expect(x).toEqual({action:'close'})
                        cb()
                ], done)


describe 'test stats changefeed: ', ->
    before(setup)
    after(teardown)

    obj = {id: null, time: null, accounts: null, accounts_created: null, \
           projects: null, projects_created: null, projects_edited: null, hub_servers:null}

    account_id = undefined
    it 'make an account', (done) ->
        async.series([
            (cb) ->
                create_accounts 1, (err, x) -> account_id=x[0]; cb(err)
        ], done)

    it 'query the stats table anonymously (get nothing, no error)', (done) ->
        db.user_query
            query : {stats:[obj]}
            cb    : (err, x) ->
                expect(err).toEqual("FATAL: get queries not allowed for table 'stats'")
                done()

    it 'query the stats table as user (get nothing, no error)', (done) ->
        db.user_query
            account_id : account_id
            query      : {stats:[obj]}
            cb         : (err, x) ->
                expect(err).toEqual("FATAL: get queries not allowed for table 'stats'")
                done()

    it 'insert some entries in the stats table', (done) ->
        db.get_stats(cb:done)

    it 'query the stats table as user and gets the one entry', (done) ->
        db.user_query
            account_id : account_id
            query      : {stats:[obj]}
            cb         : (err, x) ->
                expect(err).toEqual("FATAL: get queries not allowed for table 'stats'")
                done()

    it 'query the stats table as anon and gets the one entry', (done) ->
        db.user_query
            query      : {stats:[obj]}
            cb         : (err, x) ->
                expect(err).toEqual("FATAL: get queries not allowed for table 'stats'")
                done()


describe 'test system_notifications ', ->
    before(setup)
    after(teardown)

    obj = {id:null, time:null, text:null, priority:null, done:null}

    accounts= undefined
    it 'make two accounts', (done) ->
        async.series([
            (cb) ->
                create_accounts 2, (err, x) -> accounts=x; cb(err)
            (cb) ->
                db.make_user_admin(account_id: accounts[0], cb:cb)
        ], done)

    it 'reads empty table as admin, non-admin, and anon', (done) ->
        f = (account_id, cb) ->
            db.user_query
                account_id : account_id
                query      : {system_notifications: [obj]}
                cb         : (err, x) ->
                    expect(x).toEqual(system_notifications: [])
                    cb(err)
        async.map([accounts[0], accounts[1], undefined], f, done)

    obj0 = {id:misc.uuid(), time:new Date(), text:"watch out!", done:true}
    it 'tries to write as admin, non-admin and anon to system_notifications table', (done) ->
        f = (x, cb) ->
            db.user_query
                account_id : x.account_id
                query      : {system_notifications: obj0}
                cb         : (err, result) ->
                    expect(err).toEqual(x.err)
                    cb()
        async.map([{account_id:accounts[0]}, {account_id:accounts[1], err:'FATAL: user must be an admin'}, {err:'FATAL: no anonymous set queries'}], f, done)

    it 'reads non-empty table as admin, non-admin, and anon', (done) ->
        # fill in the defaults from the schema
        obj0.priority = 'low'
        f = (account_id, cb) ->
            db.user_query
                account_id : account_id
                query      : {system_notifications: [obj]}
                cb         : (err, x) ->
                    expect(x).toEqual(system_notifications: [obj0])
                    cb(err)
        async.map([accounts[0], accounts[1], undefined], f, done)


    it 'create changefeed, insert entry, and see it appear (as admin, non-admin, and anon)', (done) ->
        f = (account_id, cb) ->
            obj1 = {id:misc.uuid(), time:new Date(), text:'crazy alert!', priority:'medium', done:false}
            changefeed_id = misc.uuid()
            db.user_query
                account_id : account_id
                query      : {system_notifications: [obj]}
                changes    : changefeed_id
                cb         : changefeed_series([
                    (x, cb) ->
                        expect(x.system_notifications.length).toEqual(1)

                        db.user_query
                            account_id : accounts[0]  # as admin
                            query      : {system_notifications: [obj1]}
                            cb         : cb
                    (x, cb) ->
                        expect(x).toEqual( { action: 'insert', new_val: obj1 })

                        obj1.done = true
                        db.user_query
                            account_id : accounts[0]  # as admin
                            query      : {system_notifications: [obj1]}
                            cb         : cb
                    (x, cb) ->
                        expect(x.action).toEqual('update');
                        expect(x.new_val.done).toEqual(true);

                        db.user_query_cancel_changefeed(id:changefeed_id, cb:cb)
                    (x, cb) ->
                        expect(x).toEqual({action:'close'})
                        cb()
                ], done)

        async.mapSeries([accounts[0], accounts[1], undefined], f, done)


