text: 'use strict';
  const Config = require('../lib/Config');
  
  describe('Pointer Permissions', () => {
  
    beforeEach(() => {
      Config.get(Parse.applicationId).database.schemaCache.clear();
    });
  
    it('should work with find', (done) => {
      const config = Config.get(Parse.applicationId);
      const user = new Parse.User();
      const user2 = new Parse.User();
      user.set({
        username: 'user1',
        password: 'password'
      });
      user2.set({
        username: 'user2',
        password: 'password'
      });
      const obj = new Parse.Object('AnObject');
      const obj2 = new Parse.Object('AnObject');
  
      Parse.Object.saveAll([user, user2]).then(() => {
        obj.set('owner', user);
        obj2.set('owner', user2);
        return Parse.Object.saveAll([obj, obj2]);
      }).then(() => {
        return config.database.loadSchema().then((schema) => {
          return schema.updateClass('AnObject', {}, {readUserFields: ['owner']})
        });
      }).then(() => {
        return Parse.User.logIn('user1', 'password');
      }).then(() => {
        const q = new Parse.Query('AnObject');
        return q.find();
      }).then((res) => {
        expect(res.length).toBe(1);
        expect(res[0].id).toBe(obj.id);
        done();
      }).catch(error => {
        fail(JSON.stringify(error));
        done();
      });
    });
  
  
    it('should work with write', (done) => {
      const config = Config.get(Parse.applicationId);
      const user = new Parse.User();
      const user2 = new Parse.User();
      user.set({
        username: 'user1',
        password: 'password'
      });
      user2.set({
        username: 'user2',
        password: 'password'
      });
      const obj = new Parse.Object('AnObject');
      const obj2 = new Parse.Object('AnObject');
  
      Parse.Object.saveAll([user, user2]).then(() => {
        obj.set('owner', user);
        obj.set('reader', user2);
        obj2.set('owner', user2);
        obj2.set('reader', user);
        return Parse.Object.saveAll([obj, obj2]);
      }).then(() => {
        return config.database.loadSchema().then((schema) => {
          return schema.updateClass('AnObject', {}, {writeUserFields: ['owner'], readUserFields: ['reader', 'owner']});
        });
      }).then(() => {
        return Parse.User.logIn('user1', 'password');
      }).then(() => {
        obj2.set('hello', 'world');
        return obj2.save();
      }).then(() => {
        fail('User should not be able to update obj2');
      }, (err) => {
        // User 1 should not be able to update obj2
        expect(err.code).toBe(101);
        return Promise.resolve();
      }).then(()=> {
        obj.set('hello', 'world');
        return obj.save();
      }).then(() => {
        return Parse.User.logIn('user2', 'password');
      }, () => {
        fail('User should be able to update');
        return Promise.resolve();
      }).then(() => {
        const q = new Parse.Query('AnObject');
        return q.find();
      }, () => {
        fail('should login with user 2');
      }).then((res) => {
        expect(res.length).toBe(2);
        res.forEach((result) => {
          if (result.id == obj.id) {
            expect(result.get('hello')).toBe('world');
          } else {
            expect(result.id).toBe(obj2.id);
          }
        })
        done();
      }, () =>  {
        fail("failed");
        done();
      })
    });
  
    it('should let a proper user find', (done) => {
      const config = Config.get(Parse.applicationId);
      const user = new Parse.User();
      const user2 = new Parse.User();
      user.set({
        username: 'user1',
        password: 'password'
      });
      user2.set({
        username: 'user2',
        password: 'password'
      });
      const obj = new Parse.Object('AnObject');
      const obj2 = new Parse.Object('AnObject');
      user.signUp().then(() => {
        return user2.signUp()
      }).then(() => {
        Parse.User.logOut();
      }).then(() => {
        obj.set('owner', user);
        return Parse.Object.saveAll([obj, obj2]);
      }).then(() => {
        return config.database.loadSchema().then((schema) => {
          return schema.updateClass('AnObject', {}, {find: {}, get:{}, readUserFields: ['owner']})
        });
      }).then(() => {
        const q = new Parse.Query('AnObject');
        return q.find();
      }).then((res) => {
        expect(res.length).toBe(0);
      }).then(() => {
        return Parse.User.logIn('user2', 'password');
      }).then(() => {
        const q = new Parse.Query('AnObject');
        return q.find();
      }).then((res) => {
        expect(res.length).toBe(0);
        const q = new Parse.Query('AnObject');
        return q.get(obj.id);
      }).then(() => {
        fail('User 2 should not get the obj1 object');
      }, (err) => {
        expect(err.code).toBe(101);
        expect(err.message).toBe('Object not found.');
        return Promise.resolve();
      }).then(() => {
        return Parse.User.logIn('user1', 'password');
      }).then(() => {
        const q = new Parse.Query('AnObject');
        return q.find();
      }).then((res) => {
        expect(res.length).toBe(1);
        done();
      }).catch((err) => {
        jfail(err);
        fail('should not fail');
        done();
      })
    });
  
    it('should query on pointer permission enabled column', (done) => {
      const config = Config.get(Parse.applicationId);
      const user = new Parse.User();
      const user2 = new Parse.User();
      user.set({
        username: 'user1',
        password: 'password'
      });
      user2.set({
        username: 'user2',
        password: 'password'
      });
      const obj = new Parse.Object('AnObject');
      const obj2 = new Parse.Object('AnObject');
      user.signUp().then(() => {
        return user2.signUp()
      }).then(() => {
        Parse.User.logOut();
      }).then(() => {
        obj.set('owner', user);
        return Parse.Object.saveAll([obj, obj2]);
      }).then(() => {
        return config.database.loadSchema().then((schema) => {
          return schema.updateClass('AnObject', {}, {find: {}, get:{}, readUserFields: ['owner']})
        });
      }).then(() => {
        return Parse.User.logIn('user1', 'password');
      }).then(() => {
        const q = new Parse.Query('AnObject');
        q.equalTo('owner', user2);
        return q.find();
      }).then((res) => {
        expect(res.length).toBe(0);
        done();
      }).catch((err) => {
        jfail(err);
        fail('should not fail');
        done();
      })
    });
  
    it('should not allow creating objects', (done) => {
      const config = Config.get(Parse.applicationId);
      const user = new Parse.User();
      user.set({
        username: 'user1',
        password: 'password'
      });
      const obj = new Parse.Object('AnObject');
      user.save().then(() => {
        return config.database.loadSchema().then((schema) => {
          return schema.addClassIfNotExists('AnObject', {owner: {type:'Pointer', targetClass: '_User'}}, {create: {}, writeUserFields: ['owner'], readUserFields: ['owner']});
        });
      }).then(() => {
        return Parse.User.logIn('user1', 'password');
      }).then(() => {
        obj.set('owner', user);
        return obj.save();
      }).then(() => {
        fail('should not succeed');
        done();
      }, (err) => {
        expect(err.code).toBe(119);
        done();
      })
    });
  
    it('should handle multiple writeUserFields', done => {
      const config = Config.get(Parse.applicationId);
      const user = new Parse.User();
      const user2 = new Parse.User();
      user.set({
        username: 'user1',
        password: 'password'
      });
      user2.set({
        username: 'user2',
        password: 'password'
      });
      const obj = new Parse.Object('AnObject');
      Parse.Object.saveAll([user, user2])
        .then(() => {
          obj.set('owner', user);
          obj.set('otherOwner', user2);
          return obj.save();
        })
        .then(() => config.database.loadSchema())
        .then(schema => schema.updateClass('AnObject', {}, {find: {"*": true},writeUserFields: ['owner', 'otherOwner']}))
        .then(() => Parse.User.logIn('user1', 'password'))
        .then(() => obj.save({hello: 'fromUser1'}))
        .then(() => Parse.User.logIn('user2', 'password'))
        .then(() => obj.save({hello: 'fromUser2'}))
        .then(() => Parse.User.logOut())
        .then(() => {
          const q = new Parse.Query('AnObject');
          return q.first();
        })
        .then(result => {
          expect(result.get('hello')).toBe('fromUser2');
          done();
        }).catch(() => {
          fail('should not fail');
          done();
        })
    });
  
    it('should prevent creating pointer permission on missing field', (done) => {
      const config = Config.get(Parse.applicationId);
      config.database.loadSchema().then((schema) => {
        return schema.addClassIfNotExists('AnObject', {}, {create: {}, writeUserFields: ['owner'], readUserFields: ['owner']});
      }).then(() => {
        fail('should not succeed');
      }).catch((err) => {
        expect(err.code).toBe(107);
        expect(err.message).toBe("'owner' is not a valid column for class level pointer permissions writeUserFields");
        done();
      })
    });
  
    it('should prevent creating pointer permission on bad field', (done) => {
      const config = Config.get(Parse.applicationId);
      config.database.loadSchema().then((schema) => {
        return schema.addClassIfNotExists('AnObject', {owner: {type: 'String'}}, {create: {}, writeUserFields: ['owner'], readUserFields: ['owner']});
      }).then(() => {
        fail('should not succeed');
      }).catch((err) => {
        expect(err.code).toBe(107);
        expect(err.message).toBe("'owner' is not a valid column for class level pointer permissions writeUserFields");
        done();
      })
    });
  
    it('should prevent creating pointer permission on bad field', (done) => {
      const config = Config.get(Parse.applicationId);
      const object = new Parse.Object('AnObject');
      object.set('owner', 'value');
      object.save().then(() => {
        return config.database.loadSchema();
      }).then((schema) => {
        return schema.updateClass('AnObject', {}, {create: {}, writeUserFields: ['owner'], readUserFields: ['owner']});
      }).then(() => {
        fail('should not succeed');
      }).catch((err) => {
        expect(err.code).toBe(107);
        expect(err.message).toBe("'owner' is not a valid column for class level pointer permissions writeUserFields");
        done();
      })
    });
  
    it('tests CLP / Pointer Perms / ACL write (PP Locked)', (done) => {
      /*
        tests:
        CLP: update closed ({})
        PointerPerm: "owner"
        ACL: logged in user has access
  
        The owner is another user than the ACL
       */
      const config = Config.get(Parse.applicationId);
      const user = new Parse.User();
      const user2 = new Parse.User();
      user.set({
        username: 'user1',
        password: 'password'
      });
      user2.set({
        username: 'user2',
        password: 'password'
      });
      const obj = new Parse.Object('AnObject');
      Parse.Object.saveAll([user, user2]).then(() => {
        const ACL = new Parse.ACL();
        ACL.setReadAccess(user, true);
        ACL.setWriteAccess(user, true);
        obj.setACL(ACL);
        obj.set('owner', user2);
        return obj.save();
      }).then(() => {
        return config.database.loadSchema().then((schema) => {
          // Lock the update, and let only owner write
          return schema.updateClass('AnObject', {}, {update: {}, writeUserFields: ['owner']});
        });
      }).then(() => {
        return Parse.User.logIn('user1', 'password');
      }).then(() => {
        // user1 has ACL read/write but should be blocked by PP
        return obj.save({key: 'value'});
      }).then(() => {
        fail('Should not succeed saving');
        done();
      }, (err) => {
        expect(err.code).toBe(101);
        done();
      });
    });
  
    it('tests CLP / Pointer Perms / ACL write (ACL Locked)', (done) => {
      /*
        tests:
        CLP: update closed ({})
        PointerPerm: "owner"
        ACL: logged in user has access
       */
      const config = Config.get(Parse.applicationId);
      const user = new Parse.User();
      const user2 = new Parse.User();
      user.set({
        username: 'user1',
        password: 'password'
      });
      user2.set({
        username: 'user2',
        password: 'password'
      });
      const obj = new Parse.Object('AnObject');
      Parse.Object.saveAll([user, user2]).then(() => {
        const ACL = new Parse.ACL();
        ACL.setReadAccess(user, true);
        ACL.setWriteAccess(user, true);
        obj.setACL(ACL);
        obj.set('owner', user2);
        return obj.save();
      }).then(() => {
        return config.database.loadSchema().then((schema) => {
          // Lock the update, and let only owner write
          return schema.updateClass('AnObject', {}, {update: {}, writeUserFields: ['owner']});
        });
      }).then(() => {
        return Parse.User.logIn('user2', 'password');
      }).then(() => {
        // user1 has ACL read/write but should be blocked by ACL
        return obj.save({key: 'value'});
      }).then(() => {
        fail('Should not succeed saving');
        done();
      }, (err) => {
        expect(err.code).toBe(101);
        done();
      });
    });
  
    it('tests CLP / Pointer Perms / ACL write (ACL/PP OK)', (done) => {
      /*
        tests:
        CLP: update closed ({})
        PointerPerm: "owner"
        ACL: logged in user has access
       */
      const config = Config.get(Parse.applicationId);
      const user = new Parse.User();
      const user2 = new Parse.User();
      user.set({
        username: 'user1',
        password: 'password'
      });
      user2.set({
        username: 'user2',
        password: 'password'
      });
      const obj = new Parse.Object('AnObject');
      Parse.Object.saveAll([user, user2]).then(() => {
        const ACL = new Parse.ACL();
        ACL.setWriteAccess(user, true);
        ACL.setWriteAccess(user2, true);
        obj.setACL(ACL);
        obj.set('owner', user2);
        return obj.save();
      }).then(() => {
        return config.database.loadSchema().then((schema) => {
          // Lock the update, and let only owner write
          return schema.updateClass('AnObject', {}, {update: {}, writeUserFields: ['owner']});
        });
      }).then(() => {
        return Parse.User.logIn('user2', 'password');
      }).then(() => {
        // user1 has ACL read/write but should be blocked by ACL
        return obj.save({key: 'value'});
      }).then((objAgain) => {
        expect(objAgain.get('key')).toBe('value');
        done();
      }, () => {
        fail('Should not fail saving');
        done();
      });
    });
  
    it('tests CLP / Pointer Perms / ACL read (PP locked)', (done) => {
      /*
        tests:
        CLP: find/get open ({})
        PointerPerm: "owner" : read
        ACL: logged in user has access
  
        The owner is another user than the ACL
       */
      const config = Config.get(Parse.applicationId);
      const user = new Parse.User();
      const user2 = new Parse.User();
      user.set({
        username: 'user1',
        password: 'password'
      });
      user2.set({
        username: 'user2',
        password: 'password'
      });
      const obj = new Parse.Object('AnObject');
      Parse.Object.saveAll([user, user2]).then(() => {
        const ACL = new Parse.ACL();
        ACL.setReadAccess(user, true);
        ACL.setWriteAccess(user, true);
        obj.setACL(ACL);
        obj.set('owner', user2);
        return obj.save();
      }).then(() => {
        return config.database.loadSchema().then((schema) => {
          // Lock the update, and let only owner write
          return schema.updateClass('AnObject', {}, {find: {}, get: {}, readUserFields: ['owner']});
        });
      }).then(() => {
        return Parse.User.logIn('user1', 'password');
      }).then(() => {
        // user1 has ACL read/write but should be block
        return obj.fetch();
      }).then(() => {
        fail('Should not succeed saving');
        done();
      }, (err) => {
        expect(err.code).toBe(101);
        done();
      });
    });
  
    it('tests CLP / Pointer Perms / ACL read (PP/ACL OK)', (done) => {
      /*
        tests:
        CLP: find/get open ({"*": true})
        PointerPerm: "owner" : read
        ACL: logged in user has access
       */
      const config = Config.get(Parse.applicationId);
      const user = new Parse.User();
      const user2 = new Parse.User();
      user.set({
        username: 'user1',
        password: 'password'
      });
      user2.set({
        username: 'user2',
        password: 'password'
      });
      const obj = new Parse.Object('AnObject');
      Parse.Object.saveAll([user, user2]).then(() => {
        const ACL = new Parse.ACL();
        ACL.setReadAccess(user, true);
        ACL.setWriteAccess(user, true);
        ACL.setReadAccess(user2, true);
        ACL.setWriteAccess(user2, true);
        obj.setACL(ACL);
        obj.set('owner', user2);
        return obj.save();
      }).then(() => {
        return config.database.loadSchema().then((schema) => {
          // Lock the update, and let only owner write
          return schema.updateClass('AnObject', {}, {find: {"*": true}, get: {"*": true}, readUserFields: ['owner']});
        });
      }).then(() => {
        return Parse.User.logIn('user2', 'password');
      }).then(() => {
        // user1 has ACL read/write but should be block
        return obj.fetch();
      }).then((objAgain) => {
        expect(objAgain.id).toBe(obj.id);
        done();
      }, () => {
        fail('Should not fail fetching');
        done();
      });
    });
  
    it('tests CLP / Pointer Perms / ACL read (ACL locked)', (done) => {
      /*
        tests:
        CLP: find/get open ({"*": true})
        PointerPerm: "owner" : read // proper owner
        ACL: logged in user has not access
       */
      const config = Config.get(Parse.applicationId);
      const user = new Parse.User();
      const user2 = new Parse.User();
      user.set({
        username: 'user1',
        password: 'password'
      });
      user2.set({
        username: 'user2',
        password: 'password'
      });
      const obj = new Parse.Object('AnObject');
      Parse.Object.saveAll([user, user2]).then(() => {
        const ACL = new Parse.ACL();
        ACL.setReadAccess(user, true);
        ACL.setWriteAccess(user, true);
        obj.setACL(ACL);
        obj.set('owner', user2);
        return obj.save();
      }).then(() => {
        return config.database.loadSchema().then((schema) => {
          // Lock the update, and let only owner write
          return schema.updateClass('AnObject', {}, {find: {"*": true}, get: {"*": true}, readUserFields: ['owner']});
        });
      }).then(() => {
        return Parse.User.logIn('user2', 'password');
      }).then(() => {
        // user2 has ACL read/write but should be block by ACL
        return obj.fetch();
      }).then(() => {
        fail('Should not succeed saving');
        done();
      }, (err) => {
        expect(err.code).toBe(101);
        done();
      });
    });
  
    it('should let master key find objects', (done) => {
      const config = Config.get(Parse.applicationId);
      const object = new Parse.Object('AnObject');
      object.set('hello', 'world');
      return object.save().then(() => {
        return config.database.loadSchema().then((schema) => {
          // Lock the update, and let only owner write
          return schema.updateClass('AnObject', {owner: {type: 'Pointer', targetClass: '_User'}}, {find: {}, get: {}, readUserFields: ['owner']});
        });
      }).then(() => {
        const q = new Parse.Query('AnObject');
        return q.find();
      }).then(() => {
  
      }, (err) => {
        expect(err.code).toBe(101);
        return Promise.resolve();
      }).then(() => {
        const q = new Parse.Query('AnObject');
        return q.find({useMasterKey: true});
      }).then((objects) => {
        expect(objects.length).toBe(1);
        done();
      }, () => {
        fail('master key should find the object');
        done();
      })
    });
  
    it('should let master key get objects', (done) => {
      const config = Config.get(Parse.applicationId);
      const object = new Parse.Object('AnObject');
      object.set('hello', 'world');
      return object.save().then(() => {
        return config.database.loadSchema().then((schema) => {
          // Lock the update, and let only owner write
          return schema.updateClass('AnObject', {owner: {type: 'Pointer', targetClass: '_User'}}, {find: {}, get: {}, readUserFields: ['owner']});
        });
      }).then(() => {
        const q = new Parse.Query('AnObject');
        return q.get(object.id);
      }).then(() => {
  
      }, (err) => {
        expect(err.code).toBe(101);
        return Promise.resolve();
      }).then(() => {
        const q = new Parse.Query('AnObject');
        return q.get(object.id, {useMasterKey: true});
      }).then((objectAgain) => {
        expect(objectAgain).not.toBeUndefined();
        expect(objectAgain.id).toBe(object.id);
        done();
      }, () => {
        fail('master key should find the object');
        done();
      })
    });
  
  
    it('should let master key update objects', (done) => {
      const config = Config.get(Parse.applicationId);
      const object = new Parse.Object('AnObject');
      object.set('hello', 'world');
      return object.save().then(() => {
        return config.database.loadSchema().then((schema) => {
          // Lock the update, and let only owner write
          return schema.updateClass('AnObject', {owner: {type: 'Pointer', targetClass: '_User'}}, {update: {}, writeUserFields: ['owner']});
        });
      }).then(() => {
        return object.save({'hello': 'bar'});
      }).then(() => {
  
      }, (err) => {
        expect(err.code).toBe(101);
        return Promise.resolve();
      }).then(() => {
        return object.save({'hello': 'baz'}, {useMasterKey: true});
      }).then((objectAgain) => {
        expect(objectAgain.get('hello')).toBe('baz');
        done();
      }, () => {
        fail('master key should save the object');
        done();
      })
    });
  
    it('should let master key delete objects', (done) => {
      const config = Config.get(Parse.applicationId);
      const object = new Parse.Object('AnObject');
      object.set('hello', 'world');
      return object.save().then(() => {
        return config.database.loadSchema().then((schema) => {
          // Lock the update, and let only owner write
          return schema.updateClass('AnObject', {owner: {type: 'Pointer', targetClass: '_User'}}, {delete: {}, writeUserFields: ['owner']});
        });
      }).then(() => {
        return object.destroy();
      }).then(() => {
        fail();
      }, (err) => {
        expect(err.code).toBe(101);
        return Promise.resolve();
      }).then(() => {
        return object.destroy({useMasterKey: true});
      }).then(() => {
        done();
      }, () => {
        fail('master key should destroy the object');
        done();
      })
    });
  
    it('should fail with invalid pointer perms', (done) => {
      const config = Config.get(Parse.applicationId);
      config.database.loadSchema().then((schema) => {
        // Lock the update, and let only owner write
        return schema.addClassIfNotExists('AnObject', {owner: {type: 'Pointer', targetClass: '_User'}}, {delete: {}, writeUserFields: 'owner'});
      }).catch((err) => {
        expect(err.code).toBe(Parse.Error.INVALID_JSON);
        done();
      });
    });
  
    it('should fail with invalid pointer perms', (done) => {
      const config = Config.get(Parse.applicationId);
      config.database.loadSchema().then((schema) => {
        // Lock the update, and let only owner write
        return schema.addClassIfNotExists('AnObject', {owner: {type: 'Pointer', targetClass: '_User'}}, {delete: {}, writeUserFields: ['owner', 'invalid']});
      }).catch((err) => {
        expect(err.code).toBe(Parse.Error.INVALID_JSON);
        done();
      });
    })
  });
  