From 6e2268f61e70b1ca7dc1946a7dc23df20043f39e Mon Sep 17 00:00:00 2001 From: BastienRdz Date: Wed, 28 Aug 2024 11:51:43 +0200 Subject: [PATCH 1/2] + Added schema validation to Jobs.run and updated tests --- server/api.js | 48 +++++++++++- tests/configSchemaValidation.js | 127 ++++++++++++++++++++++++++++++++ 2 files changed, 174 insertions(+), 1 deletion(-) create mode 100644 tests/configSchemaValidation.js diff --git a/server/api.js b/server/api.js index ac70737..c2c5e9a 100755 --- a/server/api.js +++ b/server/api.js @@ -6,6 +6,48 @@ import './imports/startup' const Jobs = {} +// Validated Jobs.run configuration schema + +const JobRunConfigSchema = { + in: Match.Maybe({ + millisecond: Match.Maybe(Number), + milliseconds: Match.Maybe(Number), + second: Match.Maybe(Number), + seconds: Match.Maybe(Number), + minute: Match.Maybe(Number), + minutes: Match.Maybe(Number), + hour: Match.Maybe(Number), + hours: Match.Maybe(Number), + day: Match.Maybe(Number), + days: Match.Maybe(Number), + month: Match.Maybe(Number), + months: Match.Maybe(Number), + year: Match.Maybe(Number), + years: Match.Maybe(Number), + }), + on: Match.Maybe({ + millisecond: Match.Maybe(Number), + milliseconds: Match.Maybe(Number), + second: Match.Maybe(Number), + seconds: Match.Maybe(Number), + minute: Match.Maybe(Number), + minutes: Match.Maybe(Number), + hour: Match.Maybe(Number), + hours: Match.Maybe(Number), + day: Match.Maybe(Number), + days: Match.Maybe(Number), + month: Match.Maybe(Number), + months: Match.Maybe(Number), + year: Match.Maybe(Number), + years: Match.Maybe(Number), + }), + date: Match.Maybe(Date), + priority: Match.Maybe(Number), + unique: Match.Maybe(Boolean), + singular: Match.Maybe(Boolean), + callback: Match.Maybe(Function), +}; + // Configure the package (optional) Jobs.configure = function (config) { @@ -56,6 +98,10 @@ Jobs.run = async function () { check(arguments[0], String) const lastArg = arguments[arguments.length - 1]; + const config = (typeof lastArg === "object" && !Array.isArray(lastArg) && !lastArg.remote) ? lastArg : {}; + + check(config, Match.Optional(JobRunConfigSchema)); + const remote = typeof lastArg === "object" && lastArg.remote; if (Utilities.registry.data[arguments[0]] || remote) { @@ -112,7 +158,7 @@ Jobs.execute = async function (jobId, callback, force) { check(force, Match.Optional(Boolean)) // 1. Get the job - const doc = await Utilities.collection.findOneAsync({ + const doc = await Utilities.collection.findOneAsync({ _id: jobId, state: { $nin: ["success", "cancelled"] diff --git a/tests/configSchemaValidation.js b/tests/configSchemaValidation.js new file mode 100644 index 0000000..5ba1738 --- /dev/null +++ b/tests/configSchemaValidation.js @@ -0,0 +1,127 @@ +JobsTests4 = function () { + + // 0 - Clear the collection + console.log("--- 0 ---") + var clear = Jobs.clear("*") + console.log(clear) + + // 1 - Register a Job + console.log("--- 1 ---") + Jobs.register({ + "sendReminder": function (email, message) { + console.log("Sending reminder to " + email + " with message: " + message); + this.success(); + }, + "simpleJob": function () { + console.log("Running simple job with no arguments."); + this.success(); + } + }) + + // 2 - Schedule a job with valid config + console.log("--- 2 ---") + try { + var jobId1 = Jobs.run("sendReminder", "jony@apple.com", "The future is here!", { + in: { + days: 3 + }, + priority: 999, + singular: true, + callback: function () { + console.log("Callback executed successfully for jobId1"); + } + }) + console.log("JobId1 created successfully:", jobId1._id) + } catch (e) { + console.error("Error scheduling job with valid config:", e.message); + } + + // 3 - Schedule a job with only the job name (no config or additional arguments) + console.log("--- 3 ---") + try { + var jobId2 = Jobs.run("simpleJob"); + console.log("JobId2 created successfully:", jobId2._id) + } catch (e) { + console.error("Error scheduling simple job:", e.message); + } + + // 4 - Schedule a job with invalid config (typo in 'priority') + console.log("--- 4 ---") + try { + var jobId3 = Jobs.run("sendReminder", "jony@apple.com", "Hello again!", { + in: { + days: 1 + }, + prioirty: 1000, // Intentional typo: should be 'priority' + singular: true, + callback: function () { + console.log("This callback should not be executed for jobId3"); + } + }) + console.error("JobId3 should not be created due to invalid config."); + } catch (e) { + console.log("Caught expected error for invalid config:", e.message); + } + + // 5 - Schedule a job with an unsupported config key + console.log("--- 5 ---") + try { + var jobId4 = Jobs.run("sendReminder", "jony@apple.com", "Reminder!", { + in: { + hours: 2 + }, + singular: true, + unsupportedKey: "this should fail" // Unsupported key + }) + console.error("JobId4 should not be created due to unsupported config key."); + } catch (e) { + console.log("Caught expected error for unsupported config key:", e.message); + } + + // 6 - Log whatever is in the collection + console.log("--- 6 ---") + var allJobDocs = JobsInternal.Utilities.collection.find().fetch(); + console.log(allJobDocs); + + // 7 - Cancel the successfully created job (jobId1) + console.log("--- 7 ---") + if (jobId1 && jobId1._id) { + var cancel = Jobs.cancel(jobId1._id); + console.log("Cancelled jobId1:", cancel); + + // 8 - Verify the job was cancelled + var jobDoc = Jobs.get(jobId1._id); + console.log("--- 8 ---") + console.log("Job doc after cancel:") + console.log(jobDoc) + + if (jobDoc.state === "cancelled") { + console.log("Job was successfully cancelled") + } else { + console.log("Job cancel failed") + } + } else { + console.error("jobId1 was not created successfully, skipping cancel test."); + } + + // 9 - Try to cancel a job that was created without any config or arguments (jobId2) + console.log("--- 9 ---") + if (jobId2 && jobId2._id) { + var cancel2 = Jobs.cancel(jobId2._id); + console.log("Cancelled jobId2:", cancel2); + + // 10 - Verify the job was cancelled + var jobDoc2 = Jobs.get(jobId2._id); + console.log("--- 10 ---") + console.log("Job doc after cancel:") + console.log(jobDoc2) + + if (jobDoc2.state === "cancelled") { + console.log("Job was successfully cancelled") + } else { + console.log("Job cancel failed") + } + } else { + console.error("jobId2 was not created successfully, skipping cancel test."); + } +} \ No newline at end of file From d86452a2ff27d1642f90694f81b764fd98919f37 Mon Sep 17 00:00:00 2001 From: BastienRdz Date: Wed, 28 Aug 2024 19:30:04 +0200 Subject: [PATCH 2/2] * Update tests to add new configSchemaValidation tests --- package.js | 3 +- server/api.js | 1 + tests/configSchemaValidation.js | 111 ++++++++++++++++---------------- 3 files changed, 57 insertions(+), 58 deletions(-) diff --git a/package.js b/package.js index 86110fd..c1054d8 100755 --- a/package.js +++ b/package.js @@ -20,7 +20,8 @@ Package.onTest(function (api) { api.addFiles([ "tests/basic.js", + "tests/configSchemaValidation.js", "tests/internals.js", "tests/replication.js" - ]) + ]); }); diff --git a/server/api.js b/server/api.js index c2c5e9a..8ae32e0 100755 --- a/server/api.js +++ b/server/api.js @@ -46,6 +46,7 @@ const JobRunConfigSchema = { unique: Match.Maybe(Boolean), singular: Match.Maybe(Boolean), callback: Match.Maybe(Function), + data: Match.Maybe(Object), }; // Configure the package (optional) diff --git a/tests/configSchemaValidation.js b/tests/configSchemaValidation.js index 5ba1738..de917ef 100644 --- a/tests/configSchemaValidation.js +++ b/tests/configSchemaValidation.js @@ -1,12 +1,13 @@ -JobsTests4 = function () { +import { Jobs, JobsInternal } from 'meteor/msavin:sjobs'; +import { Tinytest } from 'meteor/tinytest'; +Tinytest.addAsync('Config Schema Validations', async function (test) { // 0 - Clear the collection - console.log("--- 0 ---") - var clear = Jobs.clear("*") - console.log(clear) + console.log("--- 0 ---"); + var clear = await Jobs.clear("*"); // 1 - Register a Job - console.log("--- 1 ---") + console.log("--- 1 ---"); Jobs.register({ "sendReminder": function (email, message) { console.log("Sending reminder to " + email + " with message: " + message); @@ -16,112 +17,108 @@ JobsTests4 = function () { console.log("Running simple job with no arguments."); this.success(); } - }) + }); // 2 - Schedule a job with valid config - console.log("--- 2 ---") + console.log("--- 2 ---"); + let jobId1; try { - var jobId1 = Jobs.run("sendReminder", "jony@apple.com", "The future is here!", { - in: { - days: 3 - }, + jobId1 = await Jobs.run("sendReminder", "jony@apple.com", "The future is here!", { + in: { days: 3 }, priority: 999, singular: true, callback: function () { console.log("Callback executed successfully for jobId1"); } - }) - console.log("JobId1 created successfully:", jobId1._id) + }); + console.log("JobId1 created successfully:", jobId1._id); } catch (e) { console.error("Error scheduling job with valid config:", e.message); } + // Verify that the job was created + test.isNotNull(jobId1, "JobId1 should be created successfully"); + // 3 - Schedule a job with only the job name (no config or additional arguments) - console.log("--- 3 ---") + console.log("--- 3 ---"); + let jobId2; try { - var jobId2 = Jobs.run("simpleJob"); - console.log("JobId2 created successfully:", jobId2._id) + jobId2 = await Jobs.run("simpleJob"); + console.log("JobId2 created successfully:", jobId2._id); } catch (e) { console.error("Error scheduling simple job:", e.message); } + // Verify that the job was created + test.isNotNull(jobId2, "JobId2 should be created successfully"); + // 4 - Schedule a job with invalid config (typo in 'priority') - console.log("--- 4 ---") + console.log("--- 4 ---"); try { - var jobId3 = Jobs.run("sendReminder", "jony@apple.com", "Hello again!", { - in: { - days: 1 - }, + await Jobs.run("sendReminder", "jony@apple.com", "Hello again!", { + in: { days: 1 }, prioirty: 1000, // Intentional typo: should be 'priority' singular: true, callback: function () { console.log("This callback should not be executed for jobId3"); } - }) - console.error("JobId3 should not be created due to invalid config."); + }); + test.fail("JobId3 should not be created due to invalid config."); } catch (e) { console.log("Caught expected error for invalid config:", e.message); + test.ok("Caught expected error for invalid config"); } // 5 - Schedule a job with an unsupported config key - console.log("--- 5 ---") + console.log("--- 5 ---"); try { - var jobId4 = Jobs.run("sendReminder", "jony@apple.com", "Reminder!", { - in: { - hours: 2 - }, + await Jobs.run("sendReminder", "jony@apple.com", "Reminder!", { + in: { hours: 2 }, singular: true, unsupportedKey: "this should fail" // Unsupported key - }) - console.error("JobId4 should not be created due to unsupported config key."); + }); + test.fail("JobId4 should not be created due to unsupported config key."); } catch (e) { console.log("Caught expected error for unsupported config key:", e.message); + test.ok("Caught expected error for unsupported config key"); } // 6 - Log whatever is in the collection - console.log("--- 6 ---") - var allJobDocs = JobsInternal.Utilities.collection.find().fetch(); + console.log("--- 6 ---"); + var allJobDocs = await JobsInternal.Utilities.collection.find().fetchAsync(); console.log(allJobDocs); // 7 - Cancel the successfully created job (jobId1) - console.log("--- 7 ---") + console.log("--- 7 ---"); if (jobId1 && jobId1._id) { - var cancel = Jobs.cancel(jobId1._id); + var cancel = await Jobs.cancel(jobId1._id); console.log("Cancelled jobId1:", cancel); // 8 - Verify the job was cancelled - var jobDoc = Jobs.get(jobId1._id); - console.log("--- 8 ---") - console.log("Job doc after cancel:") - console.log(jobDoc) + var jobDoc = await Jobs.get(jobId1._id); + console.log("--- 8 ---"); + console.log("Job doc after cancel:"); + console.log(jobDoc); - if (jobDoc.state === "cancelled") { - console.log("Job was successfully cancelled") - } else { - console.log("Job cancel failed") - } + test.equal(jobDoc.state, "cancelled", "Job should be successfully cancelled"); } else { - console.error("jobId1 was not created successfully, skipping cancel test."); + test.fail("jobId1 was not created successfully, skipping cancel test."); } // 9 - Try to cancel a job that was created without any config or arguments (jobId2) - console.log("--- 9 ---") + console.log("--- 9 ---"); if (jobId2 && jobId2._id) { - var cancel2 = Jobs.cancel(jobId2._id); + var cancel2 = await Jobs.cancel(jobId2._id); console.log("Cancelled jobId2:", cancel2); // 10 - Verify the job was cancelled - var jobDoc2 = Jobs.get(jobId2._id); - console.log("--- 10 ---") - console.log("Job doc after cancel:") - console.log(jobDoc2) + var jobDoc2 = await Jobs.get(jobId2._id); + console.log("--- 10 ---"); + console.log("Job doc after cancel:"); + console.log(jobDoc2); - if (jobDoc2.state === "cancelled") { - console.log("Job was successfully cancelled") - } else { - console.log("Job cancel failed") - } + test.equal(jobDoc2.state, "cancelled", "Job should be successfully cancelled"); } else { - console.error("jobId2 was not created successfully, skipping cancel test."); + test.fail("jobId2 was not created successfully, skipping cancel test."); } -} \ No newline at end of file +}); \ No newline at end of file