Skip to content

Commit

Permalink
tests: add some ajv tests
Browse files Browse the repository at this point in the history
  • Loading branch information
jankapunkt committed Mar 22, 2024
1 parent f803fc4 commit 1b6cc3b
Show file tree
Hide file tree
Showing 6 changed files with 243 additions and 118 deletions.
228 changes: 114 additions & 114 deletions package/collection2/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -327,12 +327,12 @@ function _methodMutationAsync(methodName) {

// Wrap DB write operation methods
if (Mongo.Collection.prototype.insertAsync) {
if (Meteor.isFibersDisabled) {
['insertAsync', 'updateAsync'].forEach(_methodMutationAsync.bind(this));
} else {
['insertAsync', 'updateAsync'].forEach(_methodMutation.bind(this, true));
}
if (Meteor.isFibersDisabled) {
['insertAsync', 'updateAsync'].forEach(_methodMutationAsync.bind(this));
} else {
['insertAsync', 'updateAsync'].forEach(_methodMutation.bind(this, true));
}
}

['insert', 'update'].forEach(_methodMutation.bind(this, false));

Expand Down Expand Up @@ -759,114 +759,114 @@ function defineDeny(collection, options) {
if (C2.alreadyDefined[collection._name]) {
return false; // no definition added;
}
const isLocalCollection = collection._connection === null;
const isLocalCollection = collection._connection === null;

// First, define deny functions to extend doc with the results of clean
// and auto-values. This must be done with "transform: null" or we would be
// extending a clone of doc and therefore have no effect.
const firstDeny = {
insert: function (userId, doc) {
// Referenced doc is cleaned in place
const schema = collection.c2Schema(doc);
validator.clean({ doc, schema, userId, isLocalCollection, type: 'insert' });
return false;
},
update: function (userId, doc, fields, modifier) {
// Referenced modifier is cleaned in place
const schema = collection.c2Schema(doc);
validator.clean({ userId, doc, fields, modifier, schema, type: 'update' });
return false;
},
fetch: ['_id'],
transform: null
};

if (Meteor.isFibersDisabled) {
Object.assign(firstDeny, {
insertAsync: firstDeny.insert,
updateAsync: firstDeny.update
});
}

collection.deny(firstDeny);

// Second, define deny functions to validate again on the server
// for client-initiated inserts and updates. These should be
// called after the clean/auto-value functions since we're adding
// them after. These must *not* have "transform: null" if options.transform is true because
// we need to pass the doc through any transforms to be sure
// that custom types are properly recognized for type validation.
const secondDeny = {
insert: function (userId, doc) {
// We pass the false options because we will have done them on the client if desired
doValidate({
collection,
type: 'insert',
args: [
doc,
{
trimStrings: false,
removeEmptyStrings: false,
filter: false,
autoConvert: false
},
function (error) {
if (error) {
throw new Meteor.Error(400, 'INVALID', EJSON.stringify(error.invalidKeys));
}
}
],
getAutoValues: false, // getAutoValues
userId,
isFromTrustedCode: false // isFromTrustedCode
});

return false;
},
update: function (userId, doc, fields, modifier) {
// NOTE: This will never be an upsert because client-side upserts
// are not allowed once you define allow/deny functions.
// We pass the false options because we will have done them on the client if desired
doValidate({
collection,
type: 'update',
args: [
{ _id: doc && doc._id },
modifier,
{
trimStrings: false,
removeEmptyStrings: false,
filter: false,
autoConvert: false
},
function (error) {
if (error) {
throw new Meteor.Error(400, 'INVALID', EJSON.stringify(error.invalidKeys));
}
}
],
getAutoValues: false, // getAutoValues
userId,
isFromTrustedCode: false // isFromTrustedCode
});

return false;
},
fetch: ['_id'],
...(options.transform === true ? {} : { transform: null })
};

if (Meteor.isFibersDisabled) {
Object.assign(secondDeny, {
insertAsync: secondDeny.insert,
updateAsync: secondDeny.update
});
}

collection.deny(secondDeny);

// note that we've already done this collection so that we don't do it again
// if attachSchema is called again
C2.alreadyDefined[collection._name] = true;
return true; // new definition added
// First, define deny functions to extend doc with the results of clean
// and auto-values. This must be done with "transform: null" or we would be
// extending a clone of doc and therefore have no effect.
const firstDeny = {
insert: function (userId, doc) {
// Referenced doc is cleaned in place
const schema = collection.c2Schema(doc);
validator.clean({ doc, schema, userId, isLocalCollection, type: 'insert' });
return false;
},
update: function (userId, doc, fields, modifier) {
// Referenced modifier is cleaned in place
const schema = collection.c2Schema(doc);
validator.clean({ userId, doc, fields, modifier, schema, type: 'update' });
return false;
},
fetch: ['_id'],
transform: null
};

if (Meteor.isFibersDisabled) {
Object.assign(firstDeny, {
insertAsync: firstDeny.insert,
updateAsync: firstDeny.update
});
}

collection.deny(firstDeny);

// Second, define deny functions to validate again on the server
// for client-initiated inserts and updates. These should be
// called after the clean/auto-value functions since we're adding
// them after. These must *not* have "transform: null" if options.transform is true because
// we need to pass the doc through any transforms to be sure
// that custom types are properly recognized for type validation.
const secondDeny = {
insert: function (userId, doc) {
// We pass the false options because we will have done them on the client if desired
doValidate({
collection,
type: 'insert',
args: [
doc,
{
trimStrings: false,
removeEmptyStrings: false,
filter: false,
autoConvert: false
},
function (error) {
if (error) {
throw new Meteor.Error(400, 'INVALID', EJSON.stringify(error.invalidKeys));
}
}
],
getAutoValues: false, // getAutoValues
userId,
isFromTrustedCode: false // isFromTrustedCode
});

return false;
},
update: function (userId, doc, fields, modifier) {
// NOTE: This will never be an upsert because client-side upserts
// are not allowed once you define allow/deny functions.
// We pass the false options because we will have done them on the client if desired
doValidate({
collection,
type: 'update',
args: [
{ _id: doc && doc._id },
modifier,
{
trimStrings: false,
removeEmptyStrings: false,
filter: false,
autoConvert: false
},
function (error) {
if (error) {
throw new Meteor.Error(400, 'INVALID', EJSON.stringify(error.invalidKeys));
}
}
],
getAutoValues: false, // getAutoValues
userId,
isFromTrustedCode: false // isFromTrustedCode
});

return false;
},
fetch: ['_id'],
...(options.transform === true ? {} : { transform: null })
};

if (Meteor.isFibersDisabled) {
Object.assign(secondDeny, {
insertAsync: secondDeny.insert,
updateAsync: secondDeny.update
});
}

collection.deny(secondDeny);

// note that we've already done this collection so that we don't do it again
// if attachSchema is called again
C2.alreadyDefined[collection._name] = true;
return true; // new definition added
}
60 changes: 60 additions & 0 deletions tests/ajv.tests.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/* eslint-env mocha */
import Ajv from 'ajv'
import expect from 'expect'
import { callMongoMethod } from './helper'

describe('using ajv', () => {
before(() => {
Collection2.defineValidation({
name: 'ajv',
is: schema => schema instanceof Ajv,
create: schema => {
const instance = new Ajv()
instance.definition = schema
return instance
},
extend: (s1, s2) => {
// not impl
return s2
},
clean: ({ doc, modifier, schema, userId, isLocalCollection, type }) => {
// not impl
},
validate: () => {},
freeze: false
});
})

it('attach and get simpleSchema for normal collection', function () {
;['ajvMc1', null].forEach(name => {
const mc = new Mongo.Collection(name, Meteor.isClient ? { connection: null } : undefined);

mc.attachSchema({
type: "object",
properties: { foo: { type: "string" } },
required: ["foo"],
additionalProperties: false,
});

expect(mc.c2Schema() instanceof Ajv).toBe(true);
});
});
it('handles prototype-less objects', async function () {
const prototypelessTest = new Mongo.Collection(
'prototypelessTestAjv',
Meteor.isClient ? { connection: null } : undefined
);

prototypelessTest.attachSchema({
type: "object",
properties: { foo: { type: "string" } },
required: ["foo"],
additionalProperties: false,
});

const prototypelessObject = Object.create(null);
prototypelessObject.foo = 'bar';

await callMongoMethod(prototypelessTest, 'insert', [prototypelessObject]);
});
})
3 changes: 2 additions & 1 deletion tests/main.tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ import 'meteor/aldeed:collection2/static';
import './prepare';
import './autoValue.tests'
import './clean.tests'
import './collection2.tests'
import './collection2.tests'
// import './ajv.tests'
Loading

0 comments on commit 1b6cc3b

Please sign in to comment.