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 @@
+
+
+
+
+
+ CATAPULT LMS Test Course: 005-3 Invalid Batches AU
+
+
+ CATAPULT LMS Test Course: 005-3 Invalid Batches AU
+
+
+
+
+ CATAPULT LMS Test AU: 005-3 Invalid Batches AU
+
+
+ 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 @@
+
+
+
+
+
+ CATAPULT LMS Test Course: 005-4 Valid Batches AU
+
+
+ CATAPULT LMS Test Course: 005-4 Valid Batches AU
+
+
+
+
+ CATAPULT LMS Test AU: 005-4 Valid Batches AU
+
+
+ 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);