#########################################################################
# This file is part of CoCalc: Copyright © 2020 Sagemath, Inc.
# License: AGPLv3 s.t. "Commons Clause" – see LICENSE.md for details
#########################################################################

###
Test suite for PostgreSQL interface and functionality.

COPYRIGHT : (c) 2017 SageMath, Inc.
LICENSE   : AGPLv3
###

# to run just this test, goto src/smc-hub/ and
# SMC_DB_RESET=true SMC_TEST=true time node_modules/.bin/mocha --reporter ${REPORTER:-progress} test/postgres/postgres-test.coffee

pgtest   = require('./pgtest')
db       = undefined
setup    = (cb) -> (pgtest.setup (err) -> db=pgtest.db; cb(err))
teardown = pgtest.teardown
{one_result, all_results, count_result} = require('../../postgres')

async  = require('async')
expect = require('expect')

misc = require('smc-util/misc')
{DOMAIN_URL, SITE_NAME} = require('smc-util/theme')

describe 'email verification: ', ->
    @timeout(5000)
    before(setup)
    after(teardown)
    locals =
        token         : null
        email_address : "test@test.com"
        email_address2: "test2@test.com"
        data          : null
        account_id    : null

    it "creates a random token", (done) ->
        async.waterfall([
            (cb) -> db.create_account(first_name:"A", last_name:"B", created_by:"1.2.3.4", email_address:locals.email_address, password_hash:"test", cb: cb)
            (account_id, cb) ->
                locals.account_id = account_id
                db.verify_email_create_token(account_id: account_id, cb:cb)
        ], (err, verify_info) ->
            {token} = verify_info
            expect(token.length > 5).toBe(true)
            # only lowercase, because upper/lower case in links in emails can get mangled
            for char in token
                expect(char in '0123456789abcdefghijklmnopqrstuvwxyz').toBe(true)
            locals.token = token
            done(err)
        )

    it "correctly checks the token", (done) ->
        async.series([
            (cb) ->
                db.verify_email_check_token(email_address:locals.email_address, token:locals.token, cb:cb)
            (cb) ->
                db._query
                    query : "SELECT email_address_verified FROM accounts"
                    where :
                        "email_address = $::TEXT" : locals.email_address
                    cb    : one_result 'email_address_verified', (err, data) ->
                        locals.data = data
                        cb(err)
            (cb) ->
                # and that the token is deleted
                db._query
                    query  : 'SELECT email_address_challenge FROM accounts'
                    where  :
                        "account_id = $::UUID"       : locals.account_id
                    cb     : one_result (err, data) ->
                        expect(Object.keys(data).length == 0).toBe(true)
                        cb(err)
        ], (err) ->
            expect(locals.email_address in misc.keys(locals.data)).toBe(true)
            done(err)
        )

    it "has no idea about unkown accounts", (done) ->
        db.verify_email_check_token(email_address:"other-one@test.com", token:locals.token, cb: (err) ->
            expect(!!err).toBe(true)
            expect(err.indexOf('no such account') != -1).toBe(true)
            done(undefined)  # suppress error
        )

    it "detects a wrong token", (done) ->
        async.waterfall([
            (cb) -> db.create_account(first_name:"C", last_name:"D", created_by:"1.2.3.4", email_address:locals.email_address2, password_hash:"test", cb: cb)
            (account_id, cb) -> db.verify_email_create_token(account_id: account_id, cb:cb)
            (verify_info, cb) ->
                {token, email_address} = verify_info
                expect(email_address).toBe(locals.email_address2)
                db.verify_email_check_token
                    email_address    : locals.email_address2
                    token            : "X"   # a wrong one
                    cb               : (err) ->
                        expect(err.indexOf('token does not match') != -1).toBe(true)
                        cb(undefined) # suppress error
        ], done)

    it "returns the verified email address", (done) ->
        db.verify_email_get
            account_id    : locals.account_id
            cb            : (err, x) ->
                verified = x.email_address in misc.keys(x.email_address_verified)
                expect(verified).toBe(true)
                done(err)

    it "and also answers is_verified_email correctly", (done) ->
        async.series([
            (cb) ->
                db.is_verified_email
                    email_address : locals.email_address
                    cb            : (err, verified) ->
                        expect(verified).toBe(true)
                        cb(err)

            (cb) ->
                db.is_verified_email
                    email_address : locals.email_address2
                    cb            : (err, verified) ->
                        expect(verified).toBe(false)
                        cb(err)
        ], done)

describe 'working with accounts: ', ->
    @timeout(5000)
    before(setup)
    after(teardown)
    it "checks that the account we haven't made yet doesn't already exist", (done) ->
        db.account_exists
            email_address: 'sage@example.com'
            cb:(err, exists) -> expect(!!exists).toBe(false); done(err)
    it "tries to get an account that doesn't exist", (done) ->
        db.get_account
            email_address:'sage@example.com'
            cb : (err, account) -> expect(err?).toBe(true); done()
    it "creates a new account", (done) ->
        db.create_account(first_name:"Sage", last_name:"Salvus", created_by:"1.2.3.4",\
                          email_address:"sage@example.com", password_hash:"blah", cb:done)
    it "checks the newly created account does exist", (done) ->
        db.account_exists
            email_address:'sage@example.com'
            cb:(err, exists) -> expect(!!exists).toBe(true); done(err)
    it "verifies that there is 1 account in the database via a count", (done) ->
        db.count
            table : 'accounts'
            cb : (err, n) -> expect(n).toBe(1); done(err)
    it "creates a second account", (done) ->
        db.create_account(first_name:"Mr", last_name:"Smith", created_by:"10.10.1.1",\
                          email_address:"sage-2@example.com", password_hash:"foo", cb:done)
    it "verifies that there are a total of 2 accounts in the database via the stats table", (done) ->
        db.get_stats(cb: (err, stats) -> expect(stats.accounts).toBe(2); done(err))
    it "grabs our second new account by email and checks a name and property", (done) ->
        db.get_account
            email_address:'sage-2@example.com'
            cb:(err, account) ->
                expect(account.first_name).toBe("Mr")
                expect(account.password_is_set).toBe(true)
                done(err)
    it "computes number of accounts created from 1.2.3.4", (done) ->
        db.count_accounts_created_by
            ip_address : '1.2.3.4'
            age_s      : 1000000
            cb         : (err, n) -> expect(n).toBe(1); done(err)
    it "deletes an account", (done) ->
        db.get_account
            email_address:'sage-2@example.com'
            cb : (err, account) ->
                db.delete_account
                    account_id : account.account_id
                    cb         : done
    it "checks that account is gone", (done) ->
        db.account_exists
            email_address:'sage-2@example.com'
            cb:(err, exists) -> expect(!!exists).toBe(false); done(err)
    it "creates an account with no password set", (done) ->
        db.create_account(first_name:"Simple", last_name:"Sage", created_by:"1.2.3.4",\
                          email_address:"simple@example.com", cb:done)
    it "verifies that the password_is_set field is false", (done) ->
        db.get_account
            email_address:'simple@example.com'
            cb:(err, account) -> expect(account.password_is_set).toBe(false); done(err)

describe 'working with logs: ', ->
    before(setup)
    after(teardown)

    it 'creates a log message', (done) ->
        db.log
            event : "test"
            value : "a message"
            cb    : done

    it 'gets contents of the log and checks that the message we made is there', (done) ->
        db.get_log
            start : new Date(new Date() - 10000000)
            end   : new Date()
            event : 'test'
            cb    : (err, log) ->
                expect(log.length).toBe(1)
                expect(log[0]).toEqual(event:'test', value:'a message', id:log[0].id, time:log[0].time, expire:log[0].expire)
                done(err)

    it 'checks that there is nothing "old" in the log', (done) ->
        # no old stuff
        db.get_log
            start : new Date(new Date() - 10000000)
            end   : new Date(new Date() - 1000000)
            cb    : (err, log) -> expect(log.length).toBe(0); done(err)

    account_id = '4d29eec4-c126-4f06-b679-9a661fd7bcdf'
    error = "Your internet connection is unstable/down or #{SITE_NAME} is temporarily not available. Therefore #{SITE_NAME} is not working."
    event = 'test'
    it 'logs a client error', (done) ->
        db.log_client_error
            event      : event
            error      : error
            account_id : account_id
            cb         : done
    it 'logs another client error with a different event', (done) ->
        db.log_client_error
            event      : event + "-other"
            error      : error
            account_id : account_id
            cb         : done
    it 'gets the recent error log for only one event and checks that it has only one log entry in it', (done) ->
        db.get_client_error_log
            start : new Date(new Date() - 10000000)
            end   : new Date()
            event : event
            cb    : (err, log) ->
                expect(log.length).toBe(1)
                expect(log[0]).toEqual(event:event, error:error, account_id:account_id, id:log[0].id, time:log[0].time, expire:log[0].expire)
                done(err)
    it 'gets old log entries and makes sure there are none', (done) ->
        db.get_client_error_log
            start : new Date(new Date() - 10000000)
            end   : new Date(new Date() - 1000000)
            event : event
            cb    : (err, log) -> expect(log.length).toBe(0); done(err)

describe 'testing working with blobs: ', ->
    @timeout(10000)
    beforeEach(setup)
    afterEach(teardown)
    {uuidsha1} = require('smc-util-node/misc_node')
    project_id = misc.uuid()
    it 'creating a blob and reading it', (done) ->
        blob = Buffer.from("This is a test blob")
        async.series([
            (cb) ->
                db.save_blob(uuid : uuidsha1(blob), blob : blob, project_id : project_id, cb   : cb)
            (cb) ->
                db.count
                    table : 'blobs'
                    cb    : (err, n) ->
                        expect(n).toBe(1)
                        cb(err)
            (cb) ->
                db.get_blob
                    uuid : uuidsha1(blob)
                    cb   : (err, blob2) ->
                        expect(blob2.equals(blob)).toBe(true)
                        cb(err)
        ], done)

    it 'tries to save a blob with an invalid uuid and gets an error', (done) ->
        db.save_blob
            uuid       : 'not a uuid'
            blob       : Buffer.from("This is a test blob")
            project_id : project_id
            cb         : (err) ->
                expect(err).toEqual('uuid is invalid')
                done()

    it 'save a string blob (with a null byte!), and confirms it works (properly converted to Buffer)', (done) ->
        async.series([
            (cb) ->
                db.save_blob(blob: 'my blob', project_id: project_id, cb: cb)
            (cb) ->
                db.get_blob
                    uuid : uuidsha1('my blob')
                    cb   : (err, blob2) ->
                        expect(blob2?.toString()).toEqual('my blob')
                        cb(err)
        ], done)

    it 'creating 50 blobs and verifying that 50 are in the table', (done) ->
        async.series([
            (cb) ->
                f = (n, cb) ->
                    blob = Buffer.from("x#{n}")
                    db.save_blob(uuid : uuidsha1(blob), blob : blob, project_id : project_id, cb   : cb)
                async.map([0...50], f, cb)
            (cb) ->
                db.count
                    table : 'blobs'
                    cb    : (err, n) ->
                        expect(n).toBe(50)
                        cb(err)
        ], done)

    it 'creating 5 blobs that expire in 0.01 second and 5 that do not, then wait 0.15s, delete_expired, then verify that the expired ones are gone from the table', (done) ->
        async.series([
            (cb) ->
                f = (n, cb) ->
                    blob = Buffer.from("x#{n}")
                    db.save_blob(uuid : uuidsha1(blob), blob : blob, project_id : project_id, cb : cb, ttl:if n<5 then 0.01 else 0)
                async.map([0...10], f, cb)
            (cb) ->
                setTimeout(cb, 150)
            (cb) ->
                db.delete_expired(cb:cb)
            (cb) ->
                db.count
                    table : 'blobs'
                    cb    : (err, n) ->
                        expect(n).toBe(5)
                        cb(err)
        ], done)

    it 'creating a blob that expires in 0.01 seconds, then extending it to never expire; wait, delete, and ensure it is still there', (done) ->
        blob = "a blob"
        uuid = uuidsha1(blob)
        async.series([
            (cb) ->
                db.save_blob(uuid : uuid, blob : blob, project_id : project_id, cb : cb, ttl:0.01)
            (cb) ->
                db.remove_blob_ttls(uuids:[uuid], cb:cb)
            (cb) ->
                setTimeout(cb, 100)
            (cb) ->
                db.count
                    table : 'blobs'
                    cb    : (err, n) ->
                        expect(n).toBe(1)
                        cb(err)
        ], done)

describe 'testing the hub servers registration table: ', ->
    beforeEach(setup)
    afterEach(teardown)
    it 'test registering a hub that expires in 0.05 seconds, test is right, then wait 0.1s, delete_expired, then verify done', (done) ->
        async.series([
            (cb) ->
                db.register_hub(host:"smc0", port:5000, clients:17, ttl:0.05, cb:cb)
            (cb) ->
                db.get_hub_servers cb:(err, v) ->
                    expect(v.length).toBe(1)
                    expect(v[0]).toEqual({host:"smc0-5000", port:5000, clients:17, expire:v[0].expire})
                    cb(err)
            (cb) ->
                setTimeout(cb, 150)
            (cb) ->
                db.delete_expired(cb:cb)
            (cb) ->
                db.get_hub_servers cb:(err, v) ->
                    expect(v.length).toBe(0)
                    cb(err)
        ], done)

describe 'testing the server settings table: ', ->
    before(setup)
    after(teardown)
    it 'sets a server setting', (done) ->
        db.set_server_setting
            name  : 'name'
            value : "some stuff"
            cb    : done
    it 'reads that setting back', (done) ->
        db.get_server_setting
            name : 'name'
            cb   : (err, value) ->
                expect(value).toEqual("some stuff")
                done(err)

describe 'testing the passport settings table: ', ->
    before(setup)
    after(teardown)
    it 'creates a fake passport auth', (done) ->
        db.set_passport_settings(strategy:'fake', conf:{token:'foo'},  cb: done)
    it 'verifies that the fake passport was created', (done) ->
        db.get_passport_settings
            strategy : 'fake'
            cb       : (err, value) ->
                expect(value).toEqual(token:'foo')
                done(err)
    it 'checks that it is also in the list of all passport entries', (done) ->
        db.get_all_passport_settings
            cb : (err, settings) ->
                if err
                    done(err)
                else
                    for s in settings
                        if s.strategy == 'fake' and s.conf?.token == 'foo'
                            done()
                            return
                        expect(false) # not found!

describe 'user enumeration functionality: ', ->
    before(setup)
    after(teardown)
    num = 20
    it "creates #{num} accounts", (done) ->
        f = (n, cb) ->
            db.create_account(first_name:"Sage#{n}", last_name:"Math#{n}", created_by:"1.2.3.4",\
                      email_address:"sage#{n}@sagemath.com", password_hash:"sage#{n}", cb:cb)
        async.map([0...num], f, done)
    it "searches for users using the 'sage' query", (done) ->
        db.user_search
            query : "sage"
            limit : num - 2
            cb    : (err, v) ->
                expect(v.length).toBe(num-2)
                done(err)
    it "searches for the user with email sage0@sagemath.com", (done) ->
        db.user_search
            query : "sage0@sagemath.com"
            cb    : (err, users) ->
                expect(users.length).toBe(1)
                n = 0
                data = users[0]
                delete data.created
                expect(data).toEqual(
                    email_address: "sage0@sagemath.com",
                    account_id:users[n].account_id,
                    first_name: "Sage#{n}",
                    last_name: "Math#{n}",
                    email_address_verified: null,
                    last_active: null
                )
                done(err)
    it "searches for the non-existent user with email sageBLAH@sagemath.com", (done) ->
        db.user_search
            query : "sageBLAH@sagemath.com"
            cb    : (err, users) -> expect(users.length).toBe(0); done(err)

    account_id = undefined
    it "adds another user", (done) ->
        db.create_account(first_name:"FOO", last_name:"BAR", created_by:"1.2.3.4",\
                  email_address:"foo@sagemath.com", password_hash:"sage", cb:(err, x) -> account_id=x; done(err))
    it "then checks that the new user is found by first name", (done) ->
        db.user_search
            query : "FOO"
            cb    : (err, users) -> expect(users.length).toBe(1); done(err)
    it "then checks that the new user is found by last name", (done) ->
        db.user_search
            query : "BAR"
            cb    : (err, users) -> expect(users.length).toBe(1); done(err)
    it "change that user in place", (done) ->
        db._query
            query : "UPDATE accounts"
            set   : {first_name:'VERT', last_name:'RAMP'}
            where : "account_id = $":account_id
            cb    : done
    it "then checks that the modified user is found", (done) ->
        db.user_search
            query : "VERT"
            cb    : (err, users) -> expect(users.length).toBe(1); done(err)
    it "but the previous name is not found", (done) ->
        db.user_search
            query : "BAR"
            cb    : (err, users) -> expect(users.length).toBe(0); done(err)

describe 'banning of users: ', ->
    before(setup)
    after(teardown)
    account_id = undefined
    it 'creates an account', (done) ->
        db.create_account(first_name:"Sage", last_name:"Math", created_by:"1.2.3.4",\
                          email_address:"sage@example.com", password_hash:"blah", cb:(err, x) => account_id=x; done(err))
    it 'checks by account_id that the user we just created is not banned', (done) ->
        db.is_banned_user(account_id:account_id, cb:(err,x)=>expect(x).toBe(false); done(err))
    it 'checks by email that user is not banned', (done) ->
        db.is_banned_user(email_address:"sage@example.com", cb:(err,x)=>expect(x).toBe(false); done(err))
    it 'verifies that a user that does not exist is not banned', (done) ->
        db.is_banned_user(email_address:"sageXXX@example.com", cb:(err,x)=>expect(x).toBe(false); done(err))
    it 'bans the user we created', (done) ->
        db.ban_user(account_id:account_id, cb:done)
    it 'checks they are banned by account_id', (done) ->
        db.is_banned_user(account_id:account_id, cb:(err,x)=>expect(x).toBe(true); done(err))
    it 'checks they are banned by email address', (done) ->
        db.is_banned_user(email_address:"sage@example.com", cb:(err,x)=>expect(x).toBe(true); done(err))
    it 'unbans our banned user', (done) ->
        db.unban_user(account_id:account_id, cb:done)
    it 'checks that the user we just unbanned is unbanned', (done) ->
        db.is_banned_user(account_id:account_id, cb:(err,x)=>expect(x).toBe(false); done(err))
    it 'bans our user by email address instead', (done) ->
        db.ban_user(email_address:"sage@example.com", cb:done)
    it 'then checks that banning by email address worked', (done) ->
        db.is_banned_user(account_id:account_id, cb:(err,x)=>expect(x).toBe(true); done(err))

describe 'testing the passport table: ', ->
    before(setup)
    after(teardown)
    account_id = undefined
    it 'creates an account', (done) ->
        db.create_account(first_name:"Sage", last_name:"Math", created_by:"1.2.3.4",\
                          email_address:"sage@example.com", password_hash:"blah", cb:(err, x) => account_id=x; done(err))
    it 'creates a passport', (done) ->
        db.create_passport
            account_id : account_id
            strategy   : 'google'
            id         : '929304823048'
            profile    : {email_address:"sage@example.com", avatar:'James Cameron'}
            cb         : done
    it 'checks the passport we just created exists', (done) ->
        db.passport_exists
            strategy : 'google'
            id       : '929304823048'
            cb       : (err, x) ->
                expect(x).toBe(account_id)
                done(err)
    it 'check that a non-existent passport does not exist', (done) ->
        db.passport_exists
            strategy : 'google'
            id       : 'FAKE'
            cb       : (err, x) ->
                expect(x).toBe(undefined)
                done(err)
    it 'check that a passport we created above exists directly via checking the accounts entry', (done) ->
        db.get_account
            account_id : account_id
            columns : ['passports']
            cb      : (err, x) ->
                expect(x.passports).toEqual( 'google-929304823048': { avatar: 'James Cameron', email_address: 'sage@example.com' })
                done(err)
    it 'deletes the passport we made above', (done) ->
        db.delete_passport
            account_id : account_id
            strategy : 'google'
            id       : '929304823048'
            cb       : done
    it 'verifies the passport is really gone', (done) ->
        db.passport_exists
            strategy : 'google'
            id       : '929304823048'
            cb       : (err, x) ->
                expect(x).toBe(undefined)
                done(err)
    it 'check the passport is also gone from the accounts table', (done) ->
        db.get_account
            account_id : account_id
            columns    : ['passports']
            cb         : (err, x) ->
                expect(misc.keys(x.passports).length).toEqual(0)
                done(err)
    it 'create two passports and verifies that both exist', (done) ->
        async.series([
            (cb) ->
                db.create_passport
                    account_id : account_id
                    strategy   : 'google'
                    id         : '929304823048'
                    profile    : {email_address:"sage@example.com", avatar:'James Cameron'}
                    cb         : cb
            (cb) ->
                db.create_passport
                    account_id : account_id
                    strategy   : 'facebook'
                    id         : '12346'
                    profile    : {email_address:"sage@facebook.com", avatar:'Zuck'}
                    cb         : cb
            (cb) ->
                db.get_account
                    account_id : account_id
                    columns    : ['passports']
                    cb         : (err, x) ->
                        expect(misc.keys(x.passports).length).toEqual(2)
                        cb(err)
        ], done)

describe 'testing file use notifications table: ', ->
    before(setup)
    after(teardown)
    account_id = undefined
    project_id = undefined
    path0 = "test_file"
    it 'creates an account', (done) ->
        db.create_account(first_name:"Sage", last_name:"Math", created_by:"1.2.3.4",\
                          email_address:"sage@example.com", password_hash:"blah", cb:(err, x) => account_id=x; done(err))
    it 'creates a project', (done) ->
        db.create_project(account_id:account_id, title:"Test project", description:"The description",\
                    cb:(err, x) => project_id=x; done(err))
    it "record editing of file '#{path0}'", (done) ->
        db.record_file_use(project_id: project_id, path:path0, account_id:account_id, action:"edit", cb:done)
    it "get activity for project and '#{path0}'", (done) ->
        db.get_file_use(project_id: project_id, path      : path0, max_age_s : 1000, cb:(err, x)->
            expect(x.project_id).toBe(project_id)
            expect(x.path).toBe(path0)
            expect(misc.keys(x.users)).toEqual([account_id])
            expect(misc.keys(x.users[account_id])).toEqual(['edit'])
            done(err)
        )
    it "get activity for the project and ensure there was is instance of activity", (done) ->
        db.get_file_use(project_id: project_id, max_age_s : 1000, cb:(err, x)-> expect(x.length).toBe(1); done(err))

    path1 = "another_file"
    project_id1 = undefined
    it 'creates another project', (done) ->
        db.create_project(account_id:account_id, title:"Test project 2", description:"The description 2",\
                    cb:(err, x) => project_id1=x; done(err))

    it "tests recording activity on another file '#{path1}'", (done) ->
        db.record_file_use(project_id: project_id1, path:path1, account_id:account_id, action:"edit", cb:done)

    it "gets activity only for the second project and checks there is only one entry", (done) ->
        db.get_file_use(project_id: project_id1,  max_age_s : 1000, cb:(err, x)->  expect(x.length).toBe(1); done(err))

    it "gets activity for both projects and checks there are two entries", (done) ->
        db.get_file_use(project_ids:[project_id, project_id1], max_age_s : 1000, cb:(err, x)->  expect(x.length).toBe(2); done(err))

    it "gets all info about a project", (done) ->
        db.get_project
            project_id : project_id
            cb         : (err, info) ->
                expect(info?.title).toEqual('Test project')
                expect(info?.project_id).toEqual(project_id)
                done(err)

    account_id1 = undefined
    path2 = "a_third_file"
    it 'creates another account', (done) ->
        db.create_account(first_name:"Sage1", last_name:"Math1", created_by:"1.2.3.4",\
                          email_address:"sage1@example.com", password_hash:"blah1", cb:(err, x) => account_id1=x; done(err))
    it "records activity by new user on '#{path0}", (done) ->
        db.record_file_use(project_id: project_id, path:path0, account_id:account_id1, action:"edit", cb:done)
    it "checks that there is still one activity entry for first project", (done) ->
        db.get_file_use(project_id: project_id, max_age_s : 1000, cb:(err, x)->  expect(x.length).toBe(1); done(err))
    it "checks two users are listed as editors on '#{path0}'", (done) ->
        db.get_file_use(project_id: project_id, path: path0, max_age_s : 1000, cb:(err, x)-> expect(misc.keys(x.users).length).toBe(2); done(err))
    it "records activity by new user on '#{path2}", (done) ->
        db.record_file_use(project_id: project_id, path:path2, account_id:account_id1, action:"edit", cb:done)
    it "checks that there are two activity entries now for first project", (done) ->
        db.get_file_use(project_id: project_id, max_age_s : 1000, cb:(err, x)->  expect(x.length).toBe(2); done(err))
    it "gets activity for both projects and checks there are now three entries", (done) ->
        db.get_file_use(project_ids:[project_id, project_id1], max_age_s : 1000, cb:(err, x)->  expect(x.length).toBe(3); done(err))

    it "verifies that max_age_s filter works", (done) ->
        f = () ->
            db.get_file_use(project_ids:[project_id, project_id1], max_age_s:0.05, cb:(err, x)->  expect(x.length).toBe(0); done(err))
        setTimeout(f,100)

    it "records edit action again on a file by a user and verifies that this changes the last_edited field", (done) ->
        last_edited = undefined
        async.series([
            (cb) ->
                db.get_file_use(project_id:project_id, path: path0, max_age_s:1000, cb:(err, x)-> last_edited=x.last_edited; cb(err))
            (cb) ->
                db.record_file_use(project_id:project_id, path:path0, account_id:account_id, action:"edit", cb:cb)
            (cb) ->
                db.get_file_use(project_id:project_id, path: path0, max_age_s:1000, cb:(err, x)-> expect(last_edited).toNotBe(x.last_edited); cb(err))
        ], done)

    it "records seen action on a file by a user and verifies that this does not change the last_edited field and adds seen info", (done) ->
        async.series([
            (cb) ->
                db.record_file_use(project_id:project_id, path:path0, account_id:account_id, action:"seen", cb:cb)
            (cb) ->
                db.get_file_use(project_id:project_id, path: path0, max_age_s:1000, cb:(err, x)->
                    expect(x.users[account_id].seen?).toBe(true)
                    expect(x.users[account_id].read?).toBe(false)
                    cb(err))
        ], done)


describe 'doing a "naked update"', ->
    it 'is an error', (done) ->
        db._query
            query : "UPDATE accounts SET first_name='William'"
            cb    : (err) ->
                expect(err).toEqual("ERROR -- Dangerous UPDATE or DELETE without a WHERE, TRIGGER, or INSERT:  query='UPDATE accounts SET first_name='William''")
                done()

