diff --git a/lts/__tests__/runtime.js b/lts/__tests__/runtime.js index e87e194..ebc099e 100644 --- a/lts/__tests__/runtime.js +++ b/lts/__tests__/runtime.js @@ -402,7 +402,9 @@ describe.each( describe.each( [ ["005-1-invalid-au.zip"], - ["005-2-invalid-au.zip"] + ["005-2-invalid-au.zip"], + ["005-3-invalid-batches-au.zip"], + ["005-4-valid-batches-au.zip"] ] )( "Test package: %s", diff --git a/lts/pkg/src/005-3-invalid-batches-au/005-3-invalid-batches-au.js b/lts/pkg/src/005-3-invalid-batches-au/005-3-invalid-batches-au.js new file mode 100644 index 0000000..cf59a3a --- /dev/null +++ b/lts/pkg/src/005-3-invalid-batches-au/005-3-invalid-batches-au.js @@ -0,0 +1,110 @@ +/* + Copyright 2021 Rustici Software + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ +import Helpers from "../lib/helpers"; + +const execute = async () => { + const cmi5 = await Helpers.initCmi5(); + + if (! cmi5) { + return; + } + + try { + await cmi5.postFetch(); + } + catch (ex) { + Helpers.storeResult(false, true, {msg: `Failed to start AU at POSTing to the fetch URL: ${ex}`}); + + return; + } + + try { + await cmi5.loadLMSLaunchData(); + } + catch (ex) { + Helpers.storeResult(false, true, {msg: `Failed to start AU at loading LMS Launch Data: ${ex}`}); + + return; + } + + try { + await cmi5.loadLearnerPrefs(); + } + catch (ex) { + Helpers.storeResult(false, true, {msg: `Failed to start AU at loading Learner Prefs: ${ex}`}); + + return; + } + + // Reject a double initialization within the same batch, leave as uninitialized. + const doubleInitialize = [cmi5.initializedStatement(), cmi5.initializedStatement()]; + + if (! await Helpers.sendStatement(cmi5, doubleInitialize, "9.3.0.0-2", {method: "post"})) { + return; + } + + await cmi5.initialize(); + + // Reject a double termination within the same batch, leave as unterminated. + const doubleTerminated = [cmi5.terminatedStatement(), cmi5.terminatedStatement()]; + + if (! await Helpers.sendStatement(cmi5, doubleTerminated, "9.3.0.0-2", {method: "post"})) { + return; + } + + // Reject a double passed within the same batch. + const doublePassed = [cmi5.passedStatement(), cmi5.passedStatement()]; + + if (! await Helpers.sendStatement(cmi5, doublePassed, "9.3.0.0-2", {method: "post"})) { + return; + } + + // Reject a double failed within the same batch. + const doubleFailed = [cmi5.failedStatement(), cmi5.failedStatement()]; + + if (! await Helpers.sendStatement(cmi5, doubleFailed, "9.3.0.0-2", {method: "post"})) { + return; + } + + // Reject a passed and failed within the same batch, in either order + const passedFailed = [cmi5.passedStatement(), cmi5.failedStatement()]; + + if (! await Helpers.sendStatement(cmi5, passedFailed, "9.3.0.0-3", {method: "post"})) { + return; + } + + const failedPassed = [cmi5.failedStatement(), cmi5.passedStatement()]; + + if (! await Helpers.sendStatement(cmi5, failedPassed, "9.3.0.0-3", {method: "post"})) { + return; + } + + // cmi5 defined statements should still be accepted. + try { + await cmi5.passed(); + } + catch (ex) { + Helpers.storeResult(false, true, {msg: `Failed call to passed: ${ex}`}); + + return; + } + + await Helpers.closeAU(cmi5); + + Helpers.storeResult(true, false, {msg: "All statement batches rejected successfully."}); +}; + +execute(); diff --git a/lts/pkg/src/005-3-invalid-batches-au/cmi5.xml b/lts/pkg/src/005-3-invalid-batches-au/cmi5.xml new file mode 100644 index 0000000..82724d1 --- /dev/null +++ b/lts/pkg/src/005-3-invalid-batches-au/cmi5.xml @@ -0,0 +1,39 @@ + + + + + + <langstring lang="en">CATAPULT LMS Test Course: 005-3 Invalid Batches AU</langstring> + + + CATAPULT LMS Test Course: 005-3 Invalid Batches AU + + + + + <langstring lang="en">CATAPULT LMS Test AU: 005-3 Invalid Batches AU</langstring> + + + CATAPULT LMS Test AU: 005-3 Invalid Batches AU + + index.html + + diff --git a/lts/pkg/src/005-4-valid-batches-au/005-4-valid-batches-au.js b/lts/pkg/src/005-4-valid-batches-au/005-4-valid-batches-au.js new file mode 100644 index 0000000..4e14de8 --- /dev/null +++ b/lts/pkg/src/005-4-valid-batches-au/005-4-valid-batches-au.js @@ -0,0 +1,68 @@ +/* + Copyright 2021 Rustici Software + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ +import Helpers from "../lib/helpers"; + +const execute = async () => { + const cmi5 = await Helpers.initCmi5(); + + if (! cmi5) { + return; + } + + try { + await cmi5.postFetch(); + } + catch (ex) { + Helpers.storeResult(false, true, {msg: `Failed to start AU at POSTing to the fetch URL: ${ex}`}); + + return; + } + + try { + await cmi5.loadLMSLaunchData(); + } + catch (ex) { + Helpers.storeResult(false, true, {msg: `Failed to start AU at loading LMS Launch Data: ${ex}`}); + + return; + } + + try { + await cmi5.loadLearnerPrefs(); + } + catch (ex) { + Helpers.storeResult(false, true, {msg: `Failed to start AU at loading Learner Prefs: ${ex}`}); + + return; + } + + // Do a full session in a single batch. + const fullSession = [ + cmi5.initializedStatement(), + cmi5.passedStatement(), + cmi5.completedStatement(), + cmi5.terminatedStatement() + ]; + + if (! await Helpers.sendStatement(cmi5, fullSession, "gav 123", {method: "post", shouldSucceed: true})) { + return; + } + await Helpers.returnAU(cmi5); + + Helpers.storeResult(true, false, {msg: "Valid statement batch accepted."}); +}; + +execute(); diff --git a/lts/pkg/src/005-4-valid-batches-au/cmi5.xml b/lts/pkg/src/005-4-valid-batches-au/cmi5.xml new file mode 100644 index 0000000..2426e90 --- /dev/null +++ b/lts/pkg/src/005-4-valid-batches-au/cmi5.xml @@ -0,0 +1,39 @@ + + + + + + <langstring lang="en">CATAPULT LMS Test Course: 005-4 Valid Batches AU</langstring> + + + CATAPULT LMS Test Course: 005-4 Valid Batches AU + + + + + <langstring lang="en">CATAPULT LMS Test AU: 005-4 Valid Batches AU</langstring> + + + CATAPULT LMS Test AU: 005-4 Valid Batches AU + + index.html + + diff --git a/lts/pkg/src/lib/helpers.js b/lts/pkg/src/lib/helpers.js index e4a4d2f..043a723 100644 --- a/lts/pkg/src/lib/helpers.js +++ b/lts/pkg/src/lib/helpers.js @@ -76,6 +76,9 @@ const Helpers = { return; } + return Helpers.returnAU(cmi5, loadReturnURL); + }, + returnAU: (cmi5, loadReturnURL = false) => { try { const returnURL = cmi5.getReturnURL(); diff --git a/player/service/plugins/routes/lrs.js b/player/service/plugins/routes/lrs.js index 0e01ea0..6d4a646 100644 --- a/player/service/plugins/routes/lrs.js +++ b/player/service/plugins/routes/lrs.js @@ -160,11 +160,11 @@ const Boom = require("@hapi/boom"), throw Helpers.buildViolatedReqId("9.6.3.1-4", st.id); } - if (! session.is_initialized && st.verb.id !== VERB_INITIALIZED_ID) { + if (! session.is_initialized && st.verb.id !== VERB_INITIALIZED_ID && !result.statements.includes(VERB_INITIALIZED_ID)) { throw Helpers.buildViolatedReqId("9.3.0.0-4", st.id); } - if (session.is_terminated) { + if (session.is_terminated || result.statements.includes(VERB_TERMINATED_ID)) { throw Helpers.buildViolatedReqId("9.3.0.0-5", st.id); } if (session.is_abandoned) { @@ -387,6 +387,8 @@ const Boom = require("@hapi/boom"), } } else if (resource === "agents") { + let parsedAgent; + // all agents requests require an actor, and that actor must be the launch actor try { parsedAgent = JSON.parse(req.query.agent);