├── .eslintrc.js ├── .github └── workflows │ └── player-image-workflow.yml ├── .gitignore ├── LICENSE ├── NOTICE ├── README.md ├── course_examples ├── README.md ├── masteryscore_framed │ ├── cmi5.xml │ ├── img │ │ ├── cycle1.jpg │ │ ├── earth_timescale.png │ │ ├── fault_types.svg │ │ ├── jordens_inre-numbers.svg │ │ ├── quartz1.jpg │ │ ├── strata1.jpg │ │ └── utahstrat.jpg │ ├── index.html │ ├── js │ │ ├── cmi5.min.js │ │ ├── course.js │ │ └── course_cmi5.js │ ├── style │ │ └── framed.css │ └── video │ │ └── mountains_31175.mp4 ├── masteryscore_responsive │ ├── cmi5.xml │ ├── img │ │ ├── cycle1.jpg │ │ ├── earth_timescale.png │ │ ├── fault_types.svg │ │ ├── jordens_inre-numbers.svg │ │ ├── quartz1.jpg │ │ ├── strata1.jpg │ │ └── utahstrat.jpg │ ├── index.html │ ├── js │ │ ├── cmi5.min.js │ │ ├── course.js │ │ └── course_cmi5.js │ ├── style │ │ └── base.css │ └── video │ │ └── mountains_31175.mp4 ├── multi_au_framed │ ├── cmi5.xml │ ├── img │ │ ├── cycle1.jpg │ │ ├── earth_timescale.png │ │ ├── fault_types.svg │ │ ├── jordens_inre-numbers.svg │ │ ├── quartz1.jpg │ │ ├── strata1.jpg │ │ └── utahstrat.jpg │ ├── index.html │ ├── js │ │ ├── cmi5.min.js │ │ ├── course.js │ │ └── course_cmi5.js │ ├── style │ │ └── framed.css │ └── video │ │ └── mountains_31175.mp4 ├── packages │ ├── masteryscore_framed.zip │ ├── masteryscore_responsive.zip │ ├── multi_au_framed.zip │ ├── pre_post_test_framed.zip │ ├── single_au_basic_framed.zip │ └── single_au_basic_responsive.zip ├── pre_post_test_framed │ ├── cmi5.xml │ ├── img │ │ ├── cycle1.jpg │ │ ├── earth_timescale.png │ │ ├── fault_types.svg │ │ ├── jordens_inre-numbers.svg │ │ ├── quartz1.jpg │ │ ├── strata1.jpg │ │ └── utahstrat.jpg │ ├── index.html │ ├── js │ │ ├── cmi5.min.js │ │ ├── course.js │ │ └── course_cmi5.js │ ├── post1.html │ ├── post2.html │ ├── pre1.html │ ├── pre2.html │ ├── style │ │ └── framed.css │ └── video │ │ └── mountains_31175.mp4 ├── single_au_basic_framed │ ├── cmi5.xml │ ├── img │ │ ├── cycle1.jpg │ │ ├── earth_timescale.png │ │ ├── fault_types.svg │ │ ├── jordens_inre-numbers.svg │ │ ├── quartz1.jpg │ │ ├── strata1.jpg │ │ └── utahstrat.jpg │ ├── index.html │ ├── js │ │ ├── cmi5.min.js │ │ ├── course.js │ │ └── course_cmi5.js │ ├── style │ │ └── framed.css │ └── video │ │ └── mountains_31175.mp4 └── single_au_basic_responsive │ ├── cmi5.xml │ ├── img │ ├── cycle1.jpg │ ├── earth_timescale.png │ ├── fault_types.svg │ ├── jordens_inre-numbers.svg │ ├── quartz1.jpg │ ├── strata1.jpg │ └── utahstrat.jpg │ ├── index.html │ ├── js │ ├── cmi5.min.js │ ├── course.js │ └── course_cmi5.js │ ├── style │ └── base.css │ └── video │ └── mountains_31175.mp4 ├── cts ├── .dockerignore ├── .env.example ├── Dockerfile ├── README.md ├── arch.png ├── certbot │ ├── .gitignore │ ├── check.sh │ ├── generate.sh │ ├── local │ │ ├── fullchain.pem │ │ └── privkey.pem │ └── renew.sh ├── client │ ├── .browserslistrc │ ├── .eslintrc.js │ ├── .gitignore │ ├── Dockerfile │ ├── babel.config.js │ ├── package-lock.json │ ├── package.json │ ├── public │ │ └── index.html │ ├── src │ │ ├── components │ │ │ ├── alerts.vue │ │ │ ├── app.vue │ │ │ ├── authenticated.vue │ │ │ ├── authenticated │ │ │ │ ├── admin.vue │ │ │ │ ├── admin │ │ │ │ │ ├── about.vue │ │ │ │ │ └── user │ │ │ │ │ │ └── list.vue │ │ │ │ ├── course │ │ │ │ │ ├── detail.vue │ │ │ │ │ ├── detail │ │ │ │ │ │ ├── structure.vue │ │ │ │ │ │ ├── structure │ │ │ │ │ │ │ └── node.vue │ │ │ │ │ │ └── testList.vue │ │ │ │ │ ├── list.vue │ │ │ │ │ ├── new.vue │ │ │ │ │ └── new │ │ │ │ │ │ ├── upload.vue │ │ │ │ │ │ └── xmlEditor.vue │ │ │ │ ├── navBar.vue │ │ │ │ ├── navBar │ │ │ │ │ └── testNew.vue │ │ │ │ ├── requirements │ │ │ │ │ └── list.vue │ │ │ │ ├── session │ │ │ │ │ └── detail.vue │ │ │ │ └── test │ │ │ │ │ ├── detail.vue │ │ │ │ │ ├── detail │ │ │ │ │ └── structure │ │ │ │ │ │ └── node.vue │ │ │ │ │ └── new.vue │ │ │ ├── notFound.vue │ │ │ ├── testStatus.vue │ │ │ ├── unauthenticated.vue │ │ │ └── unauthenticated │ │ │ │ ├── bootstrap.vue │ │ │ │ └── signIn.vue │ │ ├── main.js │ │ ├── main.scss │ │ ├── router │ │ │ └── index.js │ │ ├── store │ │ │ ├── alerts.js │ │ │ ├── index.js │ │ │ ├── service.js │ │ │ └── service │ │ │ │ ├── apiAccess.js │ │ │ │ ├── courses.js │ │ │ │ ├── sessions.js │ │ │ │ ├── sessions │ │ │ │ └── logs.js │ │ │ │ ├── tests.js │ │ │ │ ├── tests │ │ │ │ └── logs.js │ │ │ │ └── users.js │ │ └── styles │ │ │ └── custom-vars.scss │ └── vue.config.js ├── docker-compose.client-dev.yml ├── docker-compose.yml ├── entrypoint.sh ├── init-ssl.sh ├── init_db.sh ├── install-reqs.sh ├── migrations │ ├── 010-table-tenants.js │ ├── 020-table-users.js │ ├── 030-table-courses.js │ ├── 040-table-registrations.js │ ├── 045-table-registrations_logs.js │ ├── 050-table-sessions.js │ └── 055-table-sessions_logs.js ├── nginx │ ├── Dockerfile │ ├── default.conf │ └── proxy_headers.conf └── service │ ├── index.js │ ├── knexfile.js │ ├── lib │ ├── consts.js │ └── db.js │ ├── package-lock.json │ ├── package.json │ └── plugins │ └── routes │ ├── client.js │ └── v1 │ ├── core.js │ ├── courses.js │ ├── lib │ ├── helpers.js │ └── user.js │ ├── mgmt.js │ ├── sessions.js │ ├── tests.js │ └── users.js ├── docs ├── .gitignore ├── 404.html ├── Gemfile ├── Gemfile.lock ├── _config.yml ├── _includes │ └── header.html ├── about.markdown ├── assets │ └── main.scss ├── cts │ ├── img │ │ ├── au_card.png │ │ ├── au_configuration.png │ │ ├── au_session.png │ │ ├── au_session_requirement_violated.png │ │ ├── course_details_no_tests.png │ │ ├── course_details_structure.png │ │ ├── course_details_tests.png │ │ ├── course_list.png │ │ ├── delete_course.png │ │ ├── first_user.png │ │ ├── navbar.png │ │ ├── new_course_error.png │ │ ├── new_course_view.png │ │ ├── new_test_registration.png │ │ ├── requirements.png │ │ ├── sign_in.png │ │ ├── sign_out.png │ │ ├── test_agent_profile.png │ │ ├── test_details.png │ │ ├── test_report.png │ │ ├── test_results.png │ │ ├── test_session_initial.png │ │ ├── test_session_passed.png │ │ └── xml_editor_error.png │ └── index.markdown ├── index.markdown └── player │ ├── img │ └── api_doc.png │ └── index.markdown ├── lts ├── .gitignore ├── README.md ├── __tests__ │ ├── package.js │ └── runtime.js ├── arch.png ├── jest-puppeteer.config.js ├── jest.config.js ├── jest.setup.js ├── lib │ ├── errors.js │ ├── helpers.js │ ├── lms.catapult-player.js │ └── lms.template.js ├── package-lock.json ├── package.json ├── pkg │ ├── .eslintrc.json │ ├── src │ │ ├── 001-essentials │ │ │ ├── 001-essentials.js │ │ │ └── cmi5.xml │ │ ├── 002-allowed │ │ │ ├── 002-allowed.js │ │ │ └── cmi5.xml │ │ ├── 003-launchMethod-OwnWindow │ │ │ ├── 003-launchMethod-OwnWindow.js │ │ │ └── cmi5.xml │ │ ├── 004-1-moveOn-Completed │ │ │ ├── 004-1-moveOn-Completed.js │ │ │ └── cmi5.xml │ │ ├── 004-2-moveOn-CompletedOrPassed │ │ │ ├── 004-2-moveOn-CompletedOrPassed.js │ │ │ └── cmi5.xml │ │ ├── 004-3-moveOn-Passed │ │ │ ├── 004-3-moveOn-Passed.js │ │ │ └── cmi5.xml │ │ ├── 004-4-moveOn-CompletedOrPassed │ │ │ ├── 004-4-moveOn-CompletedOrPassed.js │ │ │ └── cmi5.xml │ │ ├── 004-5-moveOn-NotApplicable │ │ │ ├── 004-5-moveOn-NotApplicable.js │ │ │ └── cmi5.xml │ │ ├── 005-1-invalid-au │ │ │ ├── 005-1-invalid-au.js │ │ │ └── cmi5.xml │ │ ├── 005-2-invalid-au │ │ │ ├── 005-2-invalid-au.js │ │ │ └── cmi5.xml │ │ ├── 006-launchMode │ │ │ ├── 006-launchMode.js │ │ │ └── cmi5.xml │ │ ├── 007-1-multi-session │ │ │ ├── 007-1-multi-session.js │ │ │ └── cmi5.xml │ │ ├── 007-2-multi-session │ │ │ ├── 007-2-multi-session.js │ │ │ └── cmi5.xml │ │ ├── 008-1-abandoned │ │ │ ├── 008-1-abandoned.js │ │ │ └── cmi5.xml │ │ ├── 009-1-waived │ │ │ ├── 009-1-waived.js │ │ │ └── cmi5.xml │ │ ├── 101-one-thousand-aus.xml │ │ ├── 102-zip64 │ │ │ └── cmi5.xml │ │ ├── 201-1-iris-course-id.xml │ │ ├── 201-2-iris-block-id.xml │ │ ├── 201-3-iris-au-id.xml │ │ ├── 201-4-iris-objective-id.xml │ │ ├── 202-1-relative-url-no-zip.xml │ │ ├── 202-2-relative-url-no-zip.xml │ │ ├── 202-3-relative-url-no-zip.xml │ │ ├── 202-4-relative-url-no-zip.xml │ │ ├── 202-5-relative-url-no-zip.xml │ │ ├── 203-1-relative-url-no-reference │ │ │ ├── README.md │ │ │ └── cmi5.xml │ │ ├── 204-query-string-conflict-endpoint.xml │ │ ├── 205-1-duplicated-block.xml │ │ ├── 205-2-duplicated-objective.xml │ │ ├── 205-3-duplicated-au.xml │ │ ├── 206-1-invalid-au-url.xml │ │ ├── 207-1-invalid-courseStructure.xml │ │ ├── 208-1-invalid-package.md │ │ ├── 209-1-not-a-zip.zip │ │ ├── 210-1-no-cmi5-xml │ │ │ └── README.md │ │ ├── empty.js │ │ ├── index.html │ │ └── lib │ │ │ └── helpers.js │ └── webpack.config.js └── procedure.md ├── player ├── .dockerignore ├── .env.example ├── Dockerfile ├── README.md ├── arch.png ├── certbot │ └── .gitignore ├── docker-compose.yml ├── entrypoint.sh ├── migrations │ ├── 010-table-tenants.js │ ├── 020-table-courses.js │ ├── 025-table-courses_aus.js │ ├── 030-table-registrations.js │ ├── 040-table-registrations_courses_aus.js │ └── 050-table-sessions.js └── service │ ├── auth │ └── basic.modded.js │ ├── index.js │ ├── knexfile.js │ ├── lib │ └── db.js │ ├── node_modules.tar.gz │ ├── package-lock.json │ ├── package.json │ ├── plugins │ └── routes │ │ ├── content.js │ │ ├── lib │ │ ├── helpers.js │ │ ├── registration.js │ │ └── session.js │ │ ├── lrs.js │ │ ├── spec.js │ │ └── v1 │ │ ├── courses.js │ │ ├── mgmt.js │ │ ├── registrations.js │ │ └── sessions.js │ ├── tests │ ├── files │ │ └── entity.xml │ ├── lrs.spec.js │ └── xml.spec.js │ └── xsd │ └── v1 │ └── CourseStructure.xsd └── requirements ├── LICENSE ├── NOTICE ├── README.md ├── package-lock.json ├── package.json └── requirements.json /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | node_modules 3 | player/.env 4 | cts/.env 5 | cts/var 6 | lts/pkg/dist 7 | .DS_Store 8 | .idea 9 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | Catapult 2 | Copyright 2020 Rustici Software 3 | 4 | This product includes software developed at 5 | Rustici Software (http://www.rusticisoftware.com/). 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # cmi5 Advanced Testing Application and Player Underpinning Learning Technologies (CATAPULT) 2 | 3 | This repository contains the artifacts of ADL's Project CATAPULT. The resources here are intended to increase the adoption of [cmi5](https://github.com/AICC/CMI-5_Spec_Current) by providing resources and tools needed by developers, instructional designers, and procurement personnel. 4 | 5 | ## cmi5 Player 6 | 7 | The `player/` houses the implementation of a web service intended to be integrated into an LMS to provide the cmi5 launching system capabilities. It leverages an external LRS for xAPI requirement handling, but otherwise provides all validation, import, launch and other required functionality. 8 | 9 | ## Content Test Suite 10 | 11 | The `cts/` contains the implementation of a web service and web browser UI application that when used together enables end user testing of cmi5 packages. This application is targeted at instructional designers, content authoring tool developers, and learning content procurers. It also provides an example integration with the Player prototype. 12 | 13 | ## LMS Test Suite 14 | 15 | The `lts/` contains the implementation of a test suite used to validate the implementation of a cmi5 launching system within an LMS system. It contains a package library, manual test procedure document, and an automatable tool for LMS developers to use via their CI system. 16 | 17 | ## Requirements 18 | 19 | The artifact of `requirements/` is a JSON file that can be leveraged by systems to easily map from requirement identifiers to the specification language. It is made publicly available via [npm's public registry](https://www.npmjs.com/package/@cmi5/requirements). 20 | 21 | ## Course Examples 22 | 23 | The `course_examples/` is a collection of cmi5 course packages modelling potential approaches to various aspects of cmi5 implementation for content. 24 | 25 | ## Documentation 26 | 27 | The `docs/` contains the source files for building the [documentation site](https://adlnet.github.io/CATAPULT/). It contains user guides for using the tools, etc. 28 | -------------------------------------------------------------------------------- /course_examples/masteryscore_framed/cmi5.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | <langstring lang="en-US">Introduction to Geology - Framed Style - Mastery Score</langstring> 6 | 7 | 8 | 9 | This course will introduce you into the basics of geology. This includes subjects such as 10 | plate tectonics, geological materials and the history of the Earth. 11 | 12 | 13 | 14 | 16 | 17 | <langstring lang="en-US">Introduction to Geology - Framed Style - Mastery Score</langstring> 18 | 19 | 20 | 21 | This course will introduce you into the basics of geology. This includes subjects such as 22 | plate tectonics, geological materials and the history of the Earth. 23 | 24 | 25 | index.html 26 | 27 | 28 | -------------------------------------------------------------------------------- /course_examples/masteryscore_framed/img/cycle1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adlnet/CATAPULT/515126a016718d199abc008d027e89d1f9d8775f/course_examples/masteryscore_framed/img/cycle1.jpg -------------------------------------------------------------------------------- /course_examples/masteryscore_framed/img/earth_timescale.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adlnet/CATAPULT/515126a016718d199abc008d027e89d1f9d8775f/course_examples/masteryscore_framed/img/earth_timescale.png -------------------------------------------------------------------------------- /course_examples/masteryscore_framed/img/quartz1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adlnet/CATAPULT/515126a016718d199abc008d027e89d1f9d8775f/course_examples/masteryscore_framed/img/quartz1.jpg -------------------------------------------------------------------------------- /course_examples/masteryscore_framed/img/strata1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adlnet/CATAPULT/515126a016718d199abc008d027e89d1f9d8775f/course_examples/masteryscore_framed/img/strata1.jpg -------------------------------------------------------------------------------- /course_examples/masteryscore_framed/img/utahstrat.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adlnet/CATAPULT/515126a016718d199abc008d027e89d1f9d8775f/course_examples/masteryscore_framed/img/utahstrat.jpg -------------------------------------------------------------------------------- /course_examples/masteryscore_framed/video/mountains_31175.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adlnet/CATAPULT/515126a016718d199abc008d027e89d1f9d8775f/course_examples/masteryscore_framed/video/mountains_31175.mp4 -------------------------------------------------------------------------------- /course_examples/masteryscore_responsive/cmi5.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | <langstring lang="en-US">Introduction to Geology - Responsive Style - Mastery Score</langstring> 6 | 7 | 8 | 9 | This course will introduce you into the basics of geology. This includes subjects such as 10 | plate tectonics, geological materials and the history of the Earth. 11 | 12 | 13 | 14 | 16 | 17 | <langstring lang="en-US">Introduction to Geology - Responsive Style - Mastery Score</langstring> 18 | 19 | 20 | 21 | This course will introduce you into the basics of geology. This includes subjects such as 22 | plate tectonics, geological materials and the history of the Earth. 23 | 24 | 25 | index.html 26 | 27 | 28 | -------------------------------------------------------------------------------- /course_examples/masteryscore_responsive/img/cycle1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adlnet/CATAPULT/515126a016718d199abc008d027e89d1f9d8775f/course_examples/masteryscore_responsive/img/cycle1.jpg -------------------------------------------------------------------------------- /course_examples/masteryscore_responsive/img/earth_timescale.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adlnet/CATAPULT/515126a016718d199abc008d027e89d1f9d8775f/course_examples/masteryscore_responsive/img/earth_timescale.png -------------------------------------------------------------------------------- /course_examples/masteryscore_responsive/img/quartz1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adlnet/CATAPULT/515126a016718d199abc008d027e89d1f9d8775f/course_examples/masteryscore_responsive/img/quartz1.jpg -------------------------------------------------------------------------------- /course_examples/masteryscore_responsive/img/strata1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adlnet/CATAPULT/515126a016718d199abc008d027e89d1f9d8775f/course_examples/masteryscore_responsive/img/strata1.jpg -------------------------------------------------------------------------------- /course_examples/masteryscore_responsive/img/utahstrat.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adlnet/CATAPULT/515126a016718d199abc008d027e89d1f9d8775f/course_examples/masteryscore_responsive/img/utahstrat.jpg -------------------------------------------------------------------------------- /course_examples/masteryscore_responsive/video/mountains_31175.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adlnet/CATAPULT/515126a016718d199abc008d027e89d1f9d8775f/course_examples/masteryscore_responsive/video/mountains_31175.mp4 -------------------------------------------------------------------------------- /course_examples/multi_au_framed/img/cycle1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adlnet/CATAPULT/515126a016718d199abc008d027e89d1f9d8775f/course_examples/multi_au_framed/img/cycle1.jpg -------------------------------------------------------------------------------- /course_examples/multi_au_framed/img/earth_timescale.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adlnet/CATAPULT/515126a016718d199abc008d027e89d1f9d8775f/course_examples/multi_au_framed/img/earth_timescale.png -------------------------------------------------------------------------------- /course_examples/multi_au_framed/img/quartz1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adlnet/CATAPULT/515126a016718d199abc008d027e89d1f9d8775f/course_examples/multi_au_framed/img/quartz1.jpg -------------------------------------------------------------------------------- /course_examples/multi_au_framed/img/strata1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adlnet/CATAPULT/515126a016718d199abc008d027e89d1f9d8775f/course_examples/multi_au_framed/img/strata1.jpg -------------------------------------------------------------------------------- /course_examples/multi_au_framed/img/utahstrat.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adlnet/CATAPULT/515126a016718d199abc008d027e89d1f9d8775f/course_examples/multi_au_framed/img/utahstrat.jpg -------------------------------------------------------------------------------- /course_examples/multi_au_framed/video/mountains_31175.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adlnet/CATAPULT/515126a016718d199abc008d027e89d1f9d8775f/course_examples/multi_au_framed/video/mountains_31175.mp4 -------------------------------------------------------------------------------- /course_examples/packages/masteryscore_framed.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adlnet/CATAPULT/515126a016718d199abc008d027e89d1f9d8775f/course_examples/packages/masteryscore_framed.zip -------------------------------------------------------------------------------- /course_examples/packages/masteryscore_responsive.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adlnet/CATAPULT/515126a016718d199abc008d027e89d1f9d8775f/course_examples/packages/masteryscore_responsive.zip -------------------------------------------------------------------------------- /course_examples/packages/multi_au_framed.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adlnet/CATAPULT/515126a016718d199abc008d027e89d1f9d8775f/course_examples/packages/multi_au_framed.zip -------------------------------------------------------------------------------- /course_examples/packages/pre_post_test_framed.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adlnet/CATAPULT/515126a016718d199abc008d027e89d1f9d8775f/course_examples/packages/pre_post_test_framed.zip -------------------------------------------------------------------------------- /course_examples/packages/single_au_basic_framed.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adlnet/CATAPULT/515126a016718d199abc008d027e89d1f9d8775f/course_examples/packages/single_au_basic_framed.zip -------------------------------------------------------------------------------- /course_examples/packages/single_au_basic_responsive.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adlnet/CATAPULT/515126a016718d199abc008d027e89d1f9d8775f/course_examples/packages/single_au_basic_responsive.zip -------------------------------------------------------------------------------- /course_examples/pre_post_test_framed/img/cycle1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adlnet/CATAPULT/515126a016718d199abc008d027e89d1f9d8775f/course_examples/pre_post_test_framed/img/cycle1.jpg -------------------------------------------------------------------------------- /course_examples/pre_post_test_framed/img/earth_timescale.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adlnet/CATAPULT/515126a016718d199abc008d027e89d1f9d8775f/course_examples/pre_post_test_framed/img/earth_timescale.png -------------------------------------------------------------------------------- /course_examples/pre_post_test_framed/img/quartz1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adlnet/CATAPULT/515126a016718d199abc008d027e89d1f9d8775f/course_examples/pre_post_test_framed/img/quartz1.jpg -------------------------------------------------------------------------------- /course_examples/pre_post_test_framed/img/strata1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adlnet/CATAPULT/515126a016718d199abc008d027e89d1f9d8775f/course_examples/pre_post_test_framed/img/strata1.jpg -------------------------------------------------------------------------------- /course_examples/pre_post_test_framed/img/utahstrat.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adlnet/CATAPULT/515126a016718d199abc008d027e89d1f9d8775f/course_examples/pre_post_test_framed/img/utahstrat.jpg -------------------------------------------------------------------------------- /course_examples/pre_post_test_framed/video/mountains_31175.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adlnet/CATAPULT/515126a016718d199abc008d027e89d1f9d8775f/course_examples/pre_post_test_framed/video/mountains_31175.mp4 -------------------------------------------------------------------------------- /course_examples/single_au_basic_framed/cmi5.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | <langstring lang="en-US">Introduction to Geology - Framed Style</langstring> 6 | 7 | 8 | 9 | This course will introduce you into the basics of geology. This includes subjects such as 10 | plate tectonics, geological materials and the history of the Earth. 11 | 12 | 13 | 14 | 16 | 17 | <langstring lang="en-US">Introduction to Geology - Framed Style</langstring> 18 | 19 | 20 | 21 | This course will introduce you into the basics of geology. This includes subjects such as 22 | plate tectonics, geological materials and the history of the Earth. 23 | 24 | 25 | index.html 26 | 27 | 28 | -------------------------------------------------------------------------------- /course_examples/single_au_basic_framed/img/cycle1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adlnet/CATAPULT/515126a016718d199abc008d027e89d1f9d8775f/course_examples/single_au_basic_framed/img/cycle1.jpg -------------------------------------------------------------------------------- /course_examples/single_au_basic_framed/img/earth_timescale.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adlnet/CATAPULT/515126a016718d199abc008d027e89d1f9d8775f/course_examples/single_au_basic_framed/img/earth_timescale.png -------------------------------------------------------------------------------- /course_examples/single_au_basic_framed/img/quartz1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adlnet/CATAPULT/515126a016718d199abc008d027e89d1f9d8775f/course_examples/single_au_basic_framed/img/quartz1.jpg -------------------------------------------------------------------------------- /course_examples/single_au_basic_framed/img/strata1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adlnet/CATAPULT/515126a016718d199abc008d027e89d1f9d8775f/course_examples/single_au_basic_framed/img/strata1.jpg -------------------------------------------------------------------------------- /course_examples/single_au_basic_framed/img/utahstrat.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adlnet/CATAPULT/515126a016718d199abc008d027e89d1f9d8775f/course_examples/single_au_basic_framed/img/utahstrat.jpg -------------------------------------------------------------------------------- /course_examples/single_au_basic_framed/video/mountains_31175.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adlnet/CATAPULT/515126a016718d199abc008d027e89d1f9d8775f/course_examples/single_au_basic_framed/video/mountains_31175.mp4 -------------------------------------------------------------------------------- /course_examples/single_au_basic_responsive/cmi5.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | <langstring lang="en-US">Introduction to Geology - Responsive Style</langstring> 6 | 7 | 8 | 9 | This course will introduce you into the basics of geology. This includes subjects such as 10 | plate tectonics, geological materials and the history of the Earth. 11 | 12 | 13 | 14 | 16 | 17 | <langstring lang="en-US">Introduction to Geology - Responsive Style</langstring> 18 | 19 | 20 | 21 | This course will introduce you into the basics of geology. This includes subjects such as 22 | plate tectonics, geological materials and the history of the Earth. 23 | 24 | 25 | index.html 26 | 27 | 28 | -------------------------------------------------------------------------------- /course_examples/single_au_basic_responsive/img/cycle1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adlnet/CATAPULT/515126a016718d199abc008d027e89d1f9d8775f/course_examples/single_au_basic_responsive/img/cycle1.jpg -------------------------------------------------------------------------------- /course_examples/single_au_basic_responsive/img/earth_timescale.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adlnet/CATAPULT/515126a016718d199abc008d027e89d1f9d8775f/course_examples/single_au_basic_responsive/img/earth_timescale.png -------------------------------------------------------------------------------- /course_examples/single_au_basic_responsive/img/quartz1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adlnet/CATAPULT/515126a016718d199abc008d027e89d1f9d8775f/course_examples/single_au_basic_responsive/img/quartz1.jpg -------------------------------------------------------------------------------- /course_examples/single_au_basic_responsive/img/strata1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adlnet/CATAPULT/515126a016718d199abc008d027e89d1f9d8775f/course_examples/single_au_basic_responsive/img/strata1.jpg -------------------------------------------------------------------------------- /course_examples/single_au_basic_responsive/img/utahstrat.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adlnet/CATAPULT/515126a016718d199abc008d027e89d1f9d8775f/course_examples/single_au_basic_responsive/img/utahstrat.jpg -------------------------------------------------------------------------------- /course_examples/single_au_basic_responsive/video/mountains_31175.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adlnet/CATAPULT/515126a016718d199abc008d027e89d1f9d8775f/course_examples/single_au_basic_responsive/video/mountains_31175.mp4 -------------------------------------------------------------------------------- /cts/.dockerignore: -------------------------------------------------------------------------------- 1 | **/*.swp 2 | **/node_modules 3 | -------------------------------------------------------------------------------- /cts/.env.example: -------------------------------------------------------------------------------- 1 | HOSTNAME=localhost 2 | 3 | DB_HOST=rdbms 4 | DB_NAME=catapult_player 5 | DB_USERNAME=catapult 6 | DB_PASSWORD=quartz 7 | 8 | PLAYER_ROOT_PATH=/player 9 | PLAYER_BASE_URL=http://player:3398/player 10 | PLAYER_CONTENT_URL=http://localhost/player/content 11 | # PLAYER_STANDALONE_LAUNCH_URL_BASE=http://localhost/player 12 | PLAYER_KEY=cts 13 | PLAYER_SECRET=player-secret 14 | PLAYER_TOKEN_SECRET=player-token-secret 15 | 16 | CTS_SESSION_COOKIE_PASSWORD=replace_with_very-long-string_with-Mu1tip13-Specia!s 17 | 18 | LRS_ENDPOINT=http://host.docker.internal:8081/20.1.x/lrs/default/ 19 | LRS_USERNAME=dev-tools-xapi 20 | LRS_PASSWORD=dev-tools-xapi-password 21 | LRS_XAPI_VERSION=1.0.3 22 | 23 | ## Dev Only, set to 0 to allow self-signed certs 24 | NODE_TLS_REJECT_UNAUTHORIZED=1 25 | -------------------------------------------------------------------------------- /cts/Dockerfile: -------------------------------------------------------------------------------- 1 | # Copyright 2021 Rustici Software 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | FROM node:14.17.3-alpine AS client-build 16 | WORKDIR /usr/src/app 17 | COPY --chown=node:node client /usr/src/app 18 | RUN npm ci 19 | RUN npm run build 20 | 21 | FROM node:14.17.3-alpine 22 | RUN apk add dumb-init 23 | ENV NODE_ENV production 24 | WORKDIR /usr/src/app 25 | COPY --chown=node:node entrypoint.sh /usr/src/app 26 | COPY --chown=node:node service /usr/src/app 27 | COPY --chown=node:node migrations /usr/src/app/migrations 28 | COPY --chown=node:node --from=client-build /usr/src/app/dist /usr/src/app/client 29 | RUN rm package-lock.json && npm install && npm ci --only=production 30 | RUN npm install -g nodemon 31 | USER node 32 | ENTRYPOINT [] 33 | CMD ["dumb-init", "./entrypoint.sh"] 34 | EXPOSE 3399/tcp 35 | -------------------------------------------------------------------------------- /cts/README.md: -------------------------------------------------------------------------------- 1 | ![Architecture Diagram](arch.png) 2 | 3 | ## Running via Docker Compose 4 | 5 | After cloning the repository, create a `.env` file in the `cts/` directory. This file needs to include information for the following: 6 | 7 | * How to access the LRS to be used for the player *from inside the container* 8 | * The host ports where the CTS and Player can be accessed 9 | 10 | For example: 11 | 12 | ``` 13 | HOST_PORT=63399 14 | PLAYER_HOST_PORT=63398 15 | PLAYER_CONTENT_URL=http://localhost:63398/content 16 | PLAYER_KEY=cts 17 | PLAYER_SECRET="an API access secret" 18 | PLAYER_TOKEN_SECRET="some random string" 19 | LRS_ENDPOINT="http://host.docker.internal:8081/20.1.x/lrs/default/" 20 | LRS_USERNAME="dev-tools-xapi" 21 | LRS_PASSWORD="dev-tools-xapi-password" 22 | ``` 23 | 24 | Then run, 25 | 26 | docker-compose up --build -d 27 | 28 | To build and run the CTS application. Once run it will be available at the `HOST_PORT` mapped in the `.env` as above. 29 | -------------------------------------------------------------------------------- /cts/arch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adlnet/CATAPULT/515126a016718d199abc008d027e89d1f9d8775f/cts/arch.png -------------------------------------------------------------------------------- /cts/certbot/.gitignore: -------------------------------------------------------------------------------- 1 | /log 2 | /etc -------------------------------------------------------------------------------- /cts/certbot/check.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | docker-compose run certbot \ 4 | certonly --webroot \ 5 | --register-unsafely-without-email --agree-tos \ 6 | --webroot-path=/data/letsencrypt \ 7 | --staging \ 8 | -d $1 9 | 10 | -------------------------------------------------------------------------------- /cts/certbot/generate.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | rm -rf ./certbot/etc 4 | 5 | docker-compose run certbot \ 6 | certonly --webroot \ 7 | --register-unsafely-without-email --agree-tos \ 8 | --webroot-path=/data/letsencrypt \ 9 | -d $1 10 | 11 | docker-compose restart nginx 12 | -------------------------------------------------------------------------------- /cts/certbot/local/fullchain.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDlDCCAnwCCQCG915XR++czzANBgkqhkiG9w0BAQsFADCBizELMAkGA1UEBhMC 3 | QVUxETAPBgNVBAgMCFZpcmdpbmlhMRMwEQYDVQQHDApBbGV4YW5kcmlhMQwwCgYD 4 | VQQKDANBREwxDDAKBgNVBAsMA1ImRDENMAsGA1UEAwwEVHJleTEpMCcGCSqGSIb3 5 | DQEJARYadHJleS5oYXlkZW4uY3RyQGFkbG5ldC5nb3YwHhcNMTkxMDE2MTM0NDA1 6 | WhcNMjkxMDEzMTM0NDA1WjCBizELMAkGA1UEBhMCQVUxETAPBgNVBAgMCFZpcmdp 7 | bmlhMRMwEQYDVQQHDApBbGV4YW5kcmlhMQwwCgYDVQQKDANBREwxDDAKBgNVBAsM 8 | A1ImRDENMAsGA1UEAwwEVHJleTEpMCcGCSqGSIb3DQEJARYadHJleS5oYXlkZW4u 9 | Y3RyQGFkbG5ldC5nb3YwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC3 10 | GY/ZaqhO1MZ1bywEXldP3fAoHCMPBpEn3t0HO7/FYdn4rEx+BbsZ20SjFc+zRugk 11 | kBuvCdbLEKhwDHzAV4mOXbLurXi+BPOfB93I51zo9V7SoUsyspGEvopknVYcRbkP 12 | BOEuEqtTQc6ad5GqSm44v9VsbrUyczuR7CFvysOp3OAuCmaXYuwFd8Gr29ILdNHV 13 | YU3XU9KKgUeAPxoQLCUZNR0AuVJz5ewGqHKqxPNAwvAg8jfqMjIPRRdSqhYD852q 14 | ad4n18OYPUKKF3jMjYN6DeyUjqXDaUEtLqxVibBM79KubkoIM5/Dg9V20qZM5r4A 15 | K8jhVbNH4Jk4EHB711CpAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAAKqEU1Vt3kj 16 | Tag44hCMCWZDgVpu8zGTYq/2odLETJWH3Er/daUSaAUP1tvKGorLyNd7YONVcOY0 17 | t6NUrtikVZFRbGeLuupgTJxJOFux9CZCPKzaAgwvrQemBYB9WdEiE34zCAee7FCh 18 | EYkUMqzxzDNriTb46NoMJPZ1u27b2kCdBkpl4LUMjo07YcYzUSbtvrRH2FwmswfL 19 | Abq2uzXcUzbSH2TYUoz343vJjdMrkhS2ERzbEi2drU1fIyxOYn+8aeNO1yvQZkRp 20 | NYZ9AA8pbDXhtSPzX4Nvq6b5lgq81N8Uqd6zrVMgKkz662dRMmnAaMnQt00lJmrl 21 | g68QbIwM8u0= 22 | -----END CERTIFICATE----- 23 | -------------------------------------------------------------------------------- /cts/certbot/local/privkey.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEpAIBAAKCAQEAtxmP2WqoTtTGdW8sBF5XT93wKBwjDwaRJ97dBzu/xWHZ+KxM 3 | fgW7GdtEoxXPs0boJJAbrwnWyxCocAx8wFeJjl2y7q14vgTznwfdyOdc6PVe0qFL 4 | MrKRhL6KZJ1WHEW5DwThLhKrU0HOmneRqkpuOL/VbG61MnM7kewhb8rDqdzgLgpm 5 | l2LsBXfBq9vSC3TR1WFN11PSioFHgD8aECwlGTUdALlSc+XsBqhyqsTzQMLwIPI3 6 | 6jIyD0UXUqoWA/OdqmneJ9fDmD1Cihd4zI2Deg3slI6lw2lBLS6sVYmwTO/Srm5K 7 | CDOfw4PVdtKmTOa+ACvI4VWzR+CZOBBwe9dQqQIDAQABAoIBADqoGrg4/fA7I9TN 8 | J5nCq0/vtpby5JxUqz3NnJtLndTa0xxh/iNFLLpMz0E7pI03QvkrnWLG0SO+k2dJ 9 | 6NEpeey8wazKr+qX5X/l4jwROmd668SoXVHBePLAo881xDdMcsuH8adz3aTPaSN8 10 | +JJIQ+U0N2gEU1H1LnlB3zhUCbipeW00/cP8h77lheaYbBiqMxEXE3LWpqfRFo3W 11 | 139Np/vCerLIrB0IYjU/ayzPex0FBf+lJZlpeLXxVgRhli6d1ZJPws/AgIR02exm 12 | +gmSmEaZZFcpMctavUHTMcgoSmqtCjtMIqDHfjri8J3+s9ioAuoFFJnxMuCz83RC 13 | HR4Zak0CgYEA7WtWG9vqr4B3InvaKlrY6avij7+p7XYiVccx2YXXVY4UWQNfDVJr 14 | +bFXH/YG5Rr/p/bBEuypqaCsqsmLDgc9BjhOyXI/YoRLHxL4FPDf5va1UdziooVv 15 | 2BCcBdlV/5POlDxRo/qadshTZwvh8/Kygd6jatnAi4mKu2jl7qVaY/MCgYEAxW3x 16 | Z801Glk2dUPm8W8zOuDhu+kuaA4eaY9uUcrO2csEAgz+AQWrXQrCfBNLR6hXDaDh 17 | a9O7yttDIbuR70sH98ipm5k0iVpoKnL0JaDsOepABfOVazuT7gl115iGuGGKDBIf 18 | A9uanGlLF5PIvaz2VZcjTQ5gdSkUDo5KU8sFC/MCgYEA6YoV792Jow1JI0CVP+iR 19 | SnnrqN8t9gmoiJwiqwf+44eY+F5al64lzD3np+bIrYC7TqtpDIaD9fm5H16VTD1O 20 | h9nHllytgOkFRBHSujae6IBxp48R2DBN7kmYUCCTeY4ZPLOA0z5lxsYTVF/Z6a8o 21 | bJtmQGrl/dxT/TmYRAGfQoUCgYA+vC3elyWNptWYM6h6BaTYy8bPKEyXwgzF7E7Y 22 | 7MIIEOvIHwrDlmKaoSWluZfNQF/RM9MNqW1eC4hseqbnzAbPJNNIX0mDb9fzzS1p 23 | m5YU3SvU+E79kSPzmsv4Yz5D7rKoHUX/utOJaifzaQF7zC9GiU0tWikqcZAvA+X4 24 | dGW5IwKBgQCCqWCPdKmwcq+2x+C0g5uU6JyWkBHkmZy54crHu4BaKEId00D/g+C5 25 | IlJ28/C2dLdw3Ikv+3yBKPRVq9LClVxMP14h3iv0UnZ0jU91GhKSsHmF5lArhdnD 26 | 1n8+EQReI4ptXwTQ6lvGBg/5e+mHWNe+J4ONs7LIS1HW3o1aaTyfGQ== 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /cts/certbot/renew.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | docker-compose run certbot \ 4 | renew --webroot \ 5 | --register-unsafely-without-email \ 6 | --agree-tos \ 7 | --no-random-sleep-on-renew \ 8 | --webroot-path=/data/letsencrypt 9 | -------------------------------------------------------------------------------- /cts/client/.browserslistrc: -------------------------------------------------------------------------------- 1 | > 1% 2 | last 2 versions 3 | not dead 4 | -------------------------------------------------------------------------------- /cts/client/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | browser: true, 4 | node: true 5 | }, 6 | "extends": [ 7 | "plugin:vue/essential", 8 | "eslint:recommended" 9 | ], 10 | parserOptions: { 11 | parser: "babel-eslint" 12 | }, 13 | rules: { 14 | "no-console": process.env.NODE_ENV === "production" ? "warn" : "off", 15 | "no-debugger": process.env.NODE_ENV === "production" ? "warn" : "off", 16 | 17 | "vue/max-attributes-per-line": [ 18 | "error", 19 | { 20 | // this is arbitrary, the key is to allow multiple on a single line 21 | // but force multiline attributes to all be on a single line 22 | singleline: 6, 23 | multiline: { 24 | max: 1, 25 | allowFirstLine: false 26 | } 27 | } 28 | ], 29 | "vue/html-self-closing": "off" 30 | }, 31 | overrides: [ 32 | { 33 | files: ["*.vue"], 34 | rules: { 35 | indent: "off", 36 | "vue/html-indent": [ 37 | "error", 38 | 4, 39 | { 40 | baseIndent: 1 41 | } 42 | ], 43 | "vue/script-indent": [ 44 | "error", 45 | 4, 46 | { 47 | baseIndent: 1 48 | } 49 | ], 50 | "vue/name-property-casing": "off" 51 | } 52 | } 53 | ] 54 | }; 55 | -------------------------------------------------------------------------------- /cts/client/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | 6 | # local env files 7 | .env.local 8 | .env.*.local 9 | 10 | # Log files 11 | npm-debug.log* 12 | yarn-debug.log* 13 | yarn-error.log* 14 | pnpm-debug.log* 15 | 16 | # Editor directories and files 17 | .idea 18 | .vscode 19 | *.suo 20 | *.ntvs* 21 | *.njsproj 22 | *.sln 23 | *.sw? 24 | -------------------------------------------------------------------------------- /cts/client/Dockerfile: -------------------------------------------------------------------------------- 1 | # Copyright 2021 Rustici Software 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | FROM node:14.17.3-alpine AS client-build 16 | WORKDIR /usr/src/app 17 | COPY package.json package-lock.json ./ 18 | RUN npm ci 19 | ENV PATH /usr/src/app/node_modules/.bin:$PATH 20 | 21 | WORKDIR /usr/src/app/src 22 | COPY --chown=node:node . /usr/src/app/src 23 | USER node 24 | CMD ["npm", "run", "serve"] 25 | EXPOSE 3396/tcp 26 | -------------------------------------------------------------------------------- /cts/client/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@vue/cli-plugin-babel/preset' 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /cts/client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "catapult-cts-client", 3 | "version": "1.0.0", 4 | "description": "Catapult CTS UI", 5 | "repository": { 6 | "type": "git", 7 | "url": "git+https://github.com/adlnet/CATAPULT.git" 8 | }, 9 | "scripts": { 10 | "serve": "vue-cli-service serve", 11 | "build": "vue-cli-service build", 12 | "lint": "vue-cli-service lint" 13 | }, 14 | "author": "Rustici Software ", 15 | "license": "Apache-2.0", 16 | "dependencies": { 17 | "@cmi5/requirements": "^1.0.0", 18 | "@stardazed/streams-polyfill": "^2.4.0", 19 | "bootstrap-vue": "^2.21.2", 20 | "core-js": "^3.15.2", 21 | "faker": "^5.5.3", 22 | "vue": "^2.6.14", 23 | "vue-moment": "^4.1.0", 24 | "vue-router": "^3.5.2", 25 | "vuex": "^3.6.2" 26 | }, 27 | "devDependencies": { 28 | "@vue/cli-plugin-babel": "^4.5.13", 29 | "@vue/cli-plugin-eslint": "^4.5.13", 30 | "@vue/cli-plugin-router": "^4.5.13", 31 | "@vue/cli-plugin-vuex": "^4.5.13", 32 | "@vue/cli-service": "^4.5.13", 33 | "babel-eslint": "^10.1.0", 34 | "eslint": "^7.31.0", 35 | "eslint-plugin-vue": "^7.14.0", 36 | "sass": "^1.35.2", 37 | "sass-loader": "^10.2.0", 38 | "vue-template-compiler": "^2.6.14" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /cts/client/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | <%= htmlWebpackPlugin.options.title %> 9 | 10 | 11 | 14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /cts/client/src/components/alerts.vue: -------------------------------------------------------------------------------- 1 | 16 | 23 | 24 | 51 | 52 | 54 | -------------------------------------------------------------------------------- /cts/client/src/components/app.vue: -------------------------------------------------------------------------------- 1 | 16 | 26 | 27 | 48 | 49 | 59 | -------------------------------------------------------------------------------- /cts/client/src/components/authenticated.vue: -------------------------------------------------------------------------------- 1 | 16 | 32 | 33 | 60 | 61 | 67 | -------------------------------------------------------------------------------- /cts/client/src/components/authenticated/admin.vue: -------------------------------------------------------------------------------- 1 | 16 | 41 | 42 | 52 | 53 | 55 | -------------------------------------------------------------------------------- /cts/client/src/components/authenticated/admin/about.vue: -------------------------------------------------------------------------------- 1 | 16 | 26 | 27 | 32 | 33 | 35 | -------------------------------------------------------------------------------- /cts/client/src/components/authenticated/course/new.vue: -------------------------------------------------------------------------------- 1 | 16 | 35 | 36 | 48 | 49 | 51 | -------------------------------------------------------------------------------- /cts/client/src/components/authenticated/course/new/upload.vue: -------------------------------------------------------------------------------- 1 | 16 | 34 | 35 | 76 | 77 | 79 | 80 | -------------------------------------------------------------------------------- /cts/client/src/components/notFound.vue: -------------------------------------------------------------------------------- 1 | 16 | 24 | 25 | 36 | 37 | 39 | -------------------------------------------------------------------------------- /cts/client/src/components/unauthenticated.vue: -------------------------------------------------------------------------------- 1 | 16 | 40 | 41 | 62 | 63 | 81 | -------------------------------------------------------------------------------- /cts/client/src/main.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 Rustici Software 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | import Vue from "vue"; 17 | import BootstrapVue from "bootstrap-vue"; 18 | import Moment from "vue-moment"; 19 | import faker from "faker/locale/en"; 20 | import router from "./router"; 21 | import store from "./store"; 22 | import app from "./components/app.vue"; 23 | 24 | import "./main.scss"; 25 | 26 | Vue.use(Moment); 27 | Vue.use(BootstrapVue); 28 | 29 | Vue.config.productionTip = false; 30 | 31 | Object.defineProperty( 32 | Vue.prototype, 33 | "$faker", 34 | { 35 | get () { 36 | return faker; 37 | } 38 | } 39 | ); 40 | 41 | const provision = async () => { 42 | // 43 | // Try to init the credential to see if there is a cookie already 44 | // available, if so, the login screen won't be presented, otherwise 45 | // they need to enter their username and password and optionally get 46 | // a cookie set, if they don't request a cookie then a refresh of the 47 | // page will re-present the login form 48 | // 49 | await store.dispatch("service/apiAccess/initCredential"); 50 | 51 | new Vue( 52 | { 53 | router, 54 | store, 55 | render: (h) => h(app) 56 | } 57 | ).$mount("#app"); 58 | }; 59 | 60 | provision(); 61 | -------------------------------------------------------------------------------- /cts/client/src/main.scss: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 Rustici Software 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | @import "../node_modules/bootstrap/scss/bootstrap"; 17 | @import "../node_modules/bootstrap-vue/src/index.scss"; 18 | -------------------------------------------------------------------------------- /cts/client/src/store/alerts.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 Rustici Software 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | import Vue from "vue"; 17 | 18 | const initialState = () => ({ 19 | cacheContainer: {}, 20 | }); 21 | 22 | export default { 23 | namespaced: true, 24 | state: { 25 | initialState, 26 | ...initialState() 27 | }, 28 | getters: { 29 | list: (state) => (kind) => { 30 | if (! state.cacheContainer[kind]) { 31 | return; 32 | } 33 | 34 | return state.cacheContainer[kind]; 35 | } 36 | }, 37 | mutations: { 38 | add (state, {kind, payload}) { 39 | if (! state.cacheContainer[kind]) { 40 | Vue.set( 41 | state.cacheContainer, 42 | kind, 43 | [] 44 | ); 45 | } 46 | 47 | state.cacheContainer[kind].push( 48 | { 49 | id: Date.now(), 50 | variant: "danger", 51 | ...payload 52 | } 53 | ); 54 | }, 55 | remove (state, {kind, id}) { 56 | const alerts = state.cacheContainer[kind], 57 | alertIndex = alerts.findIndex((i) => i.id === id); 58 | 59 | if (alertIndex !== -1) { 60 | alerts.splice(alertIndex, 1); 61 | } 62 | } 63 | }, 64 | actions: { 65 | add: ({commit}, {kind, payload}) => { 66 | commit("add", {kind, payload}); 67 | }, 68 | remove: ({commit}, {kind, id}) => { 69 | commit("remove", {kind, id}); 70 | } 71 | } 72 | }; 73 | -------------------------------------------------------------------------------- /cts/client/src/store/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 Rustici Software 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | import Vue from "vue"; 17 | import Vuex from "vuex"; 18 | import alerts from "./alerts"; 19 | import service from "./service"; 20 | 21 | Vue.use(Vuex); 22 | 23 | export default new Vuex.Store( 24 | { 25 | namespaced: true, 26 | modules: { 27 | alerts, 28 | service 29 | }, 30 | mutations: { 31 | resetState (state) { 32 | Object.assign(state.alerts, state.alerts.initialState()); 33 | 34 | Object.assign(state.service.sessions, state.service.sessions.initialState()); 35 | Object.assign(state.service.tests, state.service.tests.initialState()); 36 | Object.assign(state.service.courses, state.service.courses.initialState()); 37 | 38 | // logout shouldn't clear isBootstrapped 39 | const apiAccess = state.service.apiAccess.initialState(); 40 | 41 | apiAccess.isBootstrapped = state.service.apiAccess.isBootstrapped; 42 | 43 | Object.assign(state.service.apiAccess, apiAccess); 44 | } 45 | } 46 | } 47 | ); 48 | -------------------------------------------------------------------------------- /cts/client/src/store/service.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 Rustici Software 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | import apiAccess from "./service/apiAccess"; 17 | import courses from "./service/courses"; 18 | import tests from "./service/tests"; 19 | import sessions from "./service/sessions"; 20 | import users from "./service/users"; 21 | 22 | export default { 23 | namespaced: true, 24 | getters: { 25 | baseApiUrl: () => process.env.VUE_APP_API_URL ? process.env.VUE_APP_API_URL : "", 26 | makeApiRequest: (state, getters) => (resource, cfg = {}) => { 27 | const fetchCfg = { 28 | ...cfg 29 | }; 30 | 31 | if (state.apiAccess.username) { 32 | fetchCfg.headers = fetchCfg.headers || {}; 33 | fetchCfg.headers.Authorization = `Basic ${Buffer.from(`${state.apiAccess.username}:${state.apiAccess.password}`).toString("base64")}`; 34 | } 35 | else { 36 | fetchCfg.credentials = "include"; 37 | } 38 | 39 | return fetch(`${getters.baseApiUrl}/api/v1/${resource}`, fetchCfg); 40 | } 41 | }, 42 | modules: { 43 | apiAccess, 44 | courses, 45 | tests, 46 | sessions, 47 | users 48 | } 49 | }; 50 | -------------------------------------------------------------------------------- /cts/client/vue.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | publicPath: "./", 3 | devServer: { 4 | port: 3396 5 | }, 6 | css: { 7 | loaderOptions: { 8 | scss: { 9 | // 10 | // make all of our custom variables available in the style 11 | // blocks of all of the components without importing in each 12 | // 13 | additionalData: `@import "~@/styles/custom-vars.scss";` 14 | } 15 | } 16 | }, 17 | chainWebpack: (config) => { 18 | config.plugin("html").tap( 19 | (args) => { 20 | args[0].title = "Catapult: CTS" 21 | 22 | return args; 23 | } 24 | ); 25 | } 26 | }; 27 | -------------------------------------------------------------------------------- /cts/docker-compose.client-dev.yml: -------------------------------------------------------------------------------- 1 | # Copyright 2021 Rustici Software 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | version: "3.8" 15 | services: 16 | client: 17 | build: client/ 18 | ports: 19 | - 3396:3396 20 | volumes: 21 | - ./client:/usr/src/app/src:ro 22 | -------------------------------------------------------------------------------- /cts/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Copyright 2021 Rustici Software 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | if [ RUN_MIGRATIONS ]; then 18 | node node_modules/.bin/knex migrate:latest 19 | fi 20 | 21 | # legacy-watch is used because it improves auto restart in our specific 22 | # use case for development, mounted volume in container, see "Application 23 | # isn't restarting" in the docs 24 | exec nodemon --legacy-watch --watch index.js --watch knexfile.js --watch lib --watch plugins index.js 25 | -------------------------------------------------------------------------------- /cts/init-ssl.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | mkdir -p ./certbot/etc/live/$1 4 | 5 | echo "[SSL] Ensured Certbot SSL Directory" 6 | 7 | cp ./certbot/local/fullchain.pem ./certbot/etc/live/$1/fullchain.pem 8 | 9 | echo "[SSL] Copied temporary SSL Cert to ./certbot/etc/live/$1/fullchain.pem" 10 | 11 | cp ./certbot/local/privkey.pem ./certbot/etc/live/$1/privkey.pem 12 | 13 | echo "[SSL] Copied temporary SSL Key to ./certbot/etc/live/$1/privkey.pem" 14 | echo "" 15 | -------------------------------------------------------------------------------- /cts/init_db.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Copyright 2021 Rustici Software 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | echo "Creating Catapult CTS Database: '$DATABASE_NAME', Player Database: '$PLAYER_DATABASE_NAME' and User: '$DATABASE_USER'"; 18 | mysql --user=root --password=$MYSQL_ROOT_PASSWORD << END 19 | 20 | CREATE DATABASE IF NOT EXISTS $DATABASE_NAME; 21 | CREATE DATABASE IF NOT EXISTS $PLAYER_DATABASE_NAME; 22 | 23 | CREATE USER '$DATABASE_USER'@'%' IDENTIFIED BY '$DATABASE_USER_PASSWORD'; 24 | GRANT ALL PRIVILEGES ON $DATABASE_NAME.* TO '$DATABASE_USER'@'%'; 25 | GRANT ALL PRIVILEGES ON $PLAYER_DATABASE_NAME.* TO '$DATABASE_USER'@'%'; 26 | FLUSH PRIVILEGES; 27 | 28 | END 29 | -------------------------------------------------------------------------------- /cts/install-reqs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Simple script to install the PERLS architecture requirements 4 | function announce() { 5 | echo "" 6 | echo "#====================================================#" 7 | echo "#" 8 | echo "# Installing $1" 9 | echo "#" 10 | echo "#====================================================#" 11 | } 12 | 13 | # Curl 14 | # 15 | announce "Curl" 16 | 17 | if ! [ -x "$(command -v curl)" ]; then 18 | 19 | # Curl is easy 20 | apt-get install curl 21 | 22 | else 23 | echo "Skipping, Curl already installed!" 24 | fi 25 | 26 | # Docker 27 | # 28 | announce "Docker" 29 | 30 | if ! [ -x "$(command -v docker)" ]; then 31 | 32 | # Docker is a bit complicated 33 | # 34 | # Add the GPG Key 35 | curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add - 36 | 37 | # Add the Docker repository to our APT sources 38 | add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" 39 | 40 | # With those added, update our packages 41 | apt-get update 42 | 43 | # Since we're up to date, get docker 44 | apt-get install -y docker-ce 45 | else 46 | echo "Skipping, docker already installed!" 47 | fi 48 | 49 | 50 | # Docker-Compose 51 | # 52 | announce "Docker-Compose" 53 | 54 | if ! [ -x "$(command -v docker-compose)" ]; then 55 | 56 | # Docker-Compose is also complicated 57 | # 58 | # Add the GPG Key 59 | curl -L https://github.com/docker/compose/releases/download/1.18.0/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose 60 | 61 | # Make sure it's executable 62 | chmod +x /usr/local/bin/docker-compose 63 | 64 | else 65 | echo "Skipping, docker-compose already installed!" 66 | fi 67 | 68 | echo "" 69 | -------------------------------------------------------------------------------- /cts/migrations/010-table-tenants.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021 Rustici Software 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | const tableName = "tenants"; 17 | 18 | exports.up = async (knex) => { 19 | await knex.schema.createTable( 20 | tableName, 21 | (table) => { 22 | table.increments("id"); 23 | table.timestamp("created_at").notNullable().defaultTo(knex.raw("CURRENT_TIMESTAMP")); 24 | table.timestamp("updated_at").notNullable().defaultTo(knex.raw("CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP")); 25 | table.string("code").notNullable().unique(); 26 | table.integer("player_tenant_id").notNullable(); 27 | } 28 | ); 29 | }; 30 | exports.down = (knex) => knex.schema.dropTable(tableName); 31 | -------------------------------------------------------------------------------- /cts/migrations/020-table-users.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021 Rustici Software 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | const tableName = "users"; 17 | 18 | exports.up = async (knex) => { 19 | await knex.schema.createTable( 20 | tableName, 21 | (table) => { 22 | table.increments("id"); 23 | table.timestamp("created_at").notNullable().defaultTo(knex.raw("CURRENT_TIMESTAMP")); 24 | table.timestamp("updated_at").notNullable().defaultTo(knex.raw("CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP")); 25 | table.integer("tenant_id").unsigned().notNullable().references("id").inTable("tenants").onUpdate("CASCADE").onDelete("RESTRICT"); 26 | table.string("username").notNullable().unique(); 27 | table.string("password").notNullable(); 28 | table.text("player_api_token"); 29 | table.json("roles").notNullable(); 30 | } 31 | ); 32 | }; 33 | exports.down = (knex) => knex.schema.dropTable(tableName); 34 | -------------------------------------------------------------------------------- /cts/migrations/030-table-courses.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021 Rustici Software 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | const tableName = "courses"; 17 | 18 | exports.up = (knex) => knex.schema.createTable( 19 | tableName, 20 | (table) => { 21 | table.increments("id"); 22 | table.timestamp("created_at").notNullable().defaultTo(knex.raw("CURRENT_TIMESTAMP")); 23 | table.timestamp("updated_at").notNullable().defaultTo(knex.raw("CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP")); 24 | table.integer("tenant_id").unsigned().notNullable().references("id").inTable("tenants").onUpdate("CASCADE").onDelete("RESTRICT"); 25 | table.integer("player_id").unsigned().notNullable().unique(); 26 | table.json("metadata").notNullable(); 27 | } 28 | ); 29 | exports.down = (knex) => knex.schema.dropTable(tableName); 30 | -------------------------------------------------------------------------------- /cts/migrations/040-table-registrations.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021 Rustici Software 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | const tableName = "registrations"; 17 | 18 | exports.up = (knex) => knex.schema.createTable( 19 | tableName, 20 | (table) => { 21 | table.increments("id"); 22 | table.timestamp("created_at").notNullable().defaultTo(knex.raw("CURRENT_TIMESTAMP")); 23 | table.timestamp("updated_at").notNullable().defaultTo(knex.raw("CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP")); 24 | table.integer("tenant_id").unsigned().notNullable().references("id").inTable("tenants").onUpdate("CASCADE").onDelete("RESTRICT"); 25 | table.integer("player_id").unsigned().notNullable().unique(); 26 | table.integer("course_id").unsigned().notNullable().references("id").inTable("courses").onUpdate("CASCADE").onDelete("CASCADE"); 27 | table.string("code").notNullable().unique(); 28 | 29 | table.json("metadata").notNullable(); 30 | } 31 | ); 32 | exports.down = (knex) => knex.schema.dropTable(tableName); 33 | -------------------------------------------------------------------------------- /cts/migrations/045-table-registrations_logs.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021 Rustici Software 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | const tableName = "registrations_logs"; 17 | 18 | exports.up = (knex) => knex.schema.createTable( 19 | tableName, 20 | (table) => { 21 | table.increments("id"); 22 | table.timestamp("created_at").notNullable().defaultTo(knex.raw("CURRENT_TIMESTAMP")); 23 | table.timestamp("updated_at").notNullable().defaultTo(knex.raw("CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP")); 24 | table.integer("tenant_id").notNullable().unsigned().references("id").inTable("tenants").onUpdate("CASCADE").onDelete("RESTRICT"); 25 | table.integer("registration_id").notNullable().unsigned().references("id").inTable("registrations").onUpdate("CASCADE").onDelete("CASCADE"); 26 | 27 | table.json("metadata").notNullable(); 28 | } 29 | ); 30 | exports.down = (knex) => knex.schema.dropTable(tableName); 31 | -------------------------------------------------------------------------------- /cts/migrations/050-table-sessions.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021 Rustici Software 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | const tableName = "sessions"; 17 | 18 | exports.up = (knex) => knex.schema.createTable( 19 | tableName, 20 | (table) => { 21 | table.increments("id"); 22 | table.timestamp("created_at").notNullable().defaultTo(knex.raw("CURRENT_TIMESTAMP")); 23 | table.timestamp("updated_at").notNullable().defaultTo(knex.raw("CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP")); 24 | table.integer("tenant_id").unsigned().notNullable().references("id").inTable("tenants").onUpdate("CASCADE").onDelete("RESTRICT"); 25 | table.integer("player_id").unsigned().notNullable().unique(); 26 | table.integer("registration_id").unsigned().notNullable().references("id").inTable("registrations").onUpdate("CASCADE").onDelete("CASCADE"); 27 | table.integer("au_index").unsigned().notNullable(); 28 | 29 | table.text("player_au_launch_url").notNullable(); 30 | table.text("player_endpoint").notNullable(); 31 | table.text("player_fetch").notNullable(); 32 | 33 | table.json("metadata").notNullable(); 34 | } 35 | ); 36 | exports.down = (knex) => knex.schema.dropTable(tableName); 37 | -------------------------------------------------------------------------------- /cts/migrations/055-table-sessions_logs.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021 Rustici Software 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | const tableName = "sessions_logs"; 17 | 18 | exports.up = (knex) => knex.schema.createTable( 19 | tableName, 20 | (table) => { 21 | table.increments("id"); 22 | table.timestamp("created_at").notNullable().defaultTo(knex.raw("CURRENT_TIMESTAMP")); 23 | table.timestamp("updated_at").notNullable().defaultTo(knex.raw("CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP")); 24 | table.integer("tenant_id").notNullable().unsigned().references("id").inTable("tenants").onUpdate("CASCADE").onDelete("RESTRICT"); 25 | table.integer("session_id").notNullable().unsigned().references("id").inTable("sessions").onUpdate("CASCADE").onDelete("CASCADE"); 26 | 27 | table.json("metadata").notNullable(); 28 | } 29 | ); 30 | exports.down = (knex) => knex.schema.dropTable(tableName); 31 | -------------------------------------------------------------------------------- /cts/nginx/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM nginx:alpine 2 | 3 | ARG HOSTNAME 4 | 5 | # Move our configuration into place 6 | # 7 | COPY default.conf /etc/nginx/nginx.conf 8 | #COPY default.conf /etc/nginx/conf.d/default.conf 9 | COPY proxy_headers.conf /etc/nginx/proxy_headers.conf 10 | 11 | # Swap our environment variables 12 | # 13 | RUN cat /etc/nginx/nginx.conf | envsubst '$HOSTNAME' | tee /tmp/nginx.conf 14 | RUN mv /tmp/nginx.conf /etc/nginx/nginx.conf 15 | 16 | -------------------------------------------------------------------------------- /cts/nginx/default.conf: -------------------------------------------------------------------------------- 1 | worker_processes 5; 2 | 3 | events { 4 | worker_connections 1024; 5 | } 6 | 7 | http { 8 | include mime.types; 9 | default_type application/octet-stream; 10 | sendfile on; 11 | keepalive_timeout 65; 12 | 13 | proxy_buffer_size 128k; 14 | proxy_buffers 4 256k; 15 | proxy_busy_buffers_size 256k; 16 | 17 | client_body_in_file_only clean; 18 | client_body_buffer_size 32; 19 | 20 | client_max_body_size 300M; 21 | 22 | server { 23 | listen 80; 24 | server_name $HOSTNAME; 25 | 26 | location / { 27 | return 301 https://$server_name$request_uri; 28 | } 29 | 30 | location ~ /.well-known/acme-challenge { 31 | allow all; 32 | root /usr/share/nginx/html; 33 | } 34 | } 35 | 36 | server { 37 | listen 443 ssl; 38 | server_name $HOSTNAME; 39 | 40 | ssl_certificate /usr/share/keys/live/$HOSTNAME/fullchain.pem; 41 | ssl_certificate_key /usr/share/keys/live/$HOSTNAME/privkey.pem; 42 | 43 | ssl_protocols TLSv1 TLSv1.1 TLSv1.2; 44 | ssl_ciphers HIGH:!aNULL:!MD5; 45 | 46 | ssl_verify_client off; 47 | proxy_ssl_server_name on; 48 | 49 | client_body_in_file_only clean; 50 | client_body_buffer_size 32K; 51 | 52 | client_max_body_size 300M; 53 | 54 | sendfile on; 55 | 56 | send_timeout 300; 57 | proxy_connect_timeout 300; 58 | proxy_send_timeout 300; 59 | proxy_read_timeout 300; 60 | 61 | location / { 62 | include proxy_headers.conf; 63 | proxy_pass http://webservice:3399; 64 | } 65 | 66 | location /player { 67 | include proxy_headers.conf; 68 | proxy_pass http://player:3398/player; 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /cts/nginx/proxy_headers.conf: -------------------------------------------------------------------------------- 1 | proxy_set_header Host $server_name; 2 | proxy_set_header X-Real-IP $remote_addr; 3 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 4 | proxy_set_header X-Forwarded-Proto https; 5 | proxy_set_header X-Forwarded-Port 443; 6 | add_header Front-End-Https on; 7 | proxy_pass_header Set-Cookie; 8 | proxy_redirect off; 9 | -------------------------------------------------------------------------------- /cts/service/lib/consts.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | AUTH_TTL_SECONDS: 8 * 60 * 60 3 | }; 4 | -------------------------------------------------------------------------------- /cts/service/lib/db.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021 Rustici Software 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | const Knex = require("knex"), 17 | KnexStringcase = require('knex-stringcase').default, 18 | KnexCfg = require("../knexfile"); 19 | 20 | module.exports = async () => { 21 | const knexCfg = await KnexCfg(); 22 | 23 | return Knex(KnexStringcase(knexCfg)); 24 | }; 25 | -------------------------------------------------------------------------------- /cts/service/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "catapult-cts-service", 3 | "version": "1.0.0", 4 | "description": "Web service to support the Catapult CTS UI", 5 | "main": "index.js", 6 | "repository": { 7 | "type": "git", 8 | "url": "git+https://github.com/adlnet/CATAPULT.git" 9 | }, 10 | "scripts": { 11 | "test": "echo \"Error: no test specified\" && exit 1", 12 | "secrets": "npx detect-secrets **/*.js" 13 | }, 14 | "author": "Rustici Software ", 15 | "license": "Apache-2.0", 16 | "dependencies": { 17 | "@cmi5/requirements": "^1.0.0", 18 | "@hapi/basic": "^6.0.0", 19 | "@hapi/boom": "^9.1.3", 20 | "@hapi/cookie": "^11.0.2", 21 | "@hapi/h2o2": "^9.1.0", 22 | "@hapi/hapi": "^20.1.5", 23 | "@hapi/hoek": "^9.2.0", 24 | "@hapi/inert": "^6.0.3", 25 | "@hapi/jwt": "^2.0.1", 26 | "@hapi/vision": "^6.1.0", 27 | "@hapi/wreck": "^17.1.0", 28 | "axios": "^1.6.8", 29 | "bcrypt": "^5.0.1", 30 | "hapi-swagger": "^14.2.1", 31 | "iri": "^1.3.0", 32 | "joi": "^17.4.1", 33 | "knex": "^0.95.7", 34 | "knex-stringcase": "^1.4.5", 35 | "mysql": "^2.18.1", 36 | "uuid": "^8.3.2", 37 | "wait-port": "^0.2.9" 38 | }, 39 | "devDependencies": { 40 | "detect-secrets": "^1.0.6" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /cts/service/plugins/routes/client.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021 Rustici Software 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | "use strict"; 17 | 18 | module.exports = { 19 | name: "catapult-cts-api-routes-client", 20 | register: (server, options) => { 21 | server.route( 22 | [ 23 | // 24 | // serve the static web client files 25 | // 26 | { 27 | method: "GET", 28 | path: "/client/{param*}", 29 | handler: { 30 | directory: { 31 | path: `${__dirname}/../../client`, 32 | listing: true 33 | } 34 | }, 35 | options: { 36 | auth: false 37 | } 38 | }, 39 | 40 | // 41 | // Handle `/` to help web UI users get to `/client/` 42 | // 43 | { 44 | method: "GET", 45 | path: "/client", 46 | handler: (req, h) => h.redirect("/client/"), 47 | options: { 48 | auth: false 49 | } 50 | } 51 | ] 52 | ); 53 | } 54 | }; 55 | -------------------------------------------------------------------------------- /cts/service/plugins/routes/v1/lib/helpers.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | doesLRSResourceEnforceConcurrency: (resourcePath) => { 3 | const concurrencyPaths = [ 4 | "activities/state", 5 | "activities/profile", 6 | "agents/profile" 7 | ]; 8 | 9 | return concurrencyPaths.includes(resourcePath); 10 | } 11 | } -------------------------------------------------------------------------------- /cts/service/plugins/routes/v1/mgmt.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021 Rustici Software 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | "use strict"; 17 | 18 | module.exports = { 19 | name: "catapult-cts-api-routes-v1-mgmt", 20 | register: (server, options) => { 21 | server.route( 22 | { 23 | method: "GET", 24 | path: "/ping", 25 | options: { 26 | tags: ["api"] 27 | }, 28 | handler: (req, h) => ({ 29 | ok: true 30 | }) 31 | }, 32 | { 33 | method: "GET", 34 | path: "/about", 35 | options: { 36 | tags: ["api"] 37 | }, 38 | handler: (req, h) => ({ 39 | description: "catapult-cts-service" 40 | }) 41 | } 42 | ); 43 | } 44 | }; 45 | -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | _site 2 | .sass-cache 3 | .jekyll-cache 4 | .jekyll-metadata 5 | .bundle 6 | vendor 7 | -------------------------------------------------------------------------------- /docs/404.html: -------------------------------------------------------------------------------- 1 | --- 2 | permalink: /404.html 3 | layout: default 4 | --- 5 | 6 | 19 | 20 |
21 |

404

22 | 23 |

Page not found :(

24 |

The requested page could not be found.

25 |
26 | -------------------------------------------------------------------------------- /docs/Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | # Hello! This is where you manage which Jekyll version is used to run. 3 | # When you want to use a different version, change it below, save the 4 | # file and run `bundle install`. Run Jekyll with `bundle exec`, like so: 5 | # 6 | # bundle exec jekyll serve 7 | # 8 | # This will help ensure the proper Jekyll version is running. 9 | # Happy Jekylling! 10 | #gem "jekyll", "~> 4.2.0" 11 | # This is the default theme for new Jekyll sites. You may change this to anything you like. 12 | gem "minima", "~> 2.5.1" 13 | # If you want to use GitHub Pages, remove the "gem "jekyll"" above and 14 | # uncomment the line below. To upgrade, run `bundle update github-pages`. 15 | gem "github-pages", "~> 217", group: :jekyll_plugins 16 | # If you have any plugins, put them here! 17 | group :jekyll_plugins do 18 | gem "jekyll-feed", "~> 0.12" 19 | end 20 | 21 | # Windows and JRuby does not include zoneinfo files, so bundle the tzinfo-data gem 22 | # and associated library. 23 | platforms :mingw, :x64_mingw, :mswin, :jruby do 24 | gem "tzinfo", "~> 1.2" 25 | gem "tzinfo-data" 26 | end 27 | 28 | # Performance-booster for watching directories on Windows 29 | gem "wdm", "~> 0.1.1", :platforms => [:mingw, :x64_mingw, :mswin] 30 | 31 | -------------------------------------------------------------------------------- /docs/_config.yml: -------------------------------------------------------------------------------- 1 | # Welcome to Jekyll! 2 | # 3 | # This config file is meant for settings that affect your whole blog, values 4 | # which you are expected to set up once and rarely edit after that. If you find 5 | # yourself editing this file very often, consider using Jekyll's data files 6 | # feature for the data you need to update frequently. 7 | # 8 | # For technical reasons, this file is *NOT* reloaded automatically when you use 9 | # 'bundle exec jekyll serve'. If you change this file, please restart the server process. 10 | # 11 | # If you need help with YAML syntax, here are some quick references for you: 12 | # https://learn-the-web.algonquindesign.ca/topics/markdown-yaml-cheat-sheet/#yaml 13 | # https://learnxinyminutes.com/docs/yaml/ 14 | # 15 | # Site settings 16 | # These are used to personalize your new site. If you look in the HTML files, 17 | # you will see them accessed via {{ site.title }}, {{ site.email }}, and so on. 18 | # You can create any custom variable you would like, and they will be accessible 19 | # in the templates via {{ site.myvariable }}. 20 | 21 | title: CATAPULT Documentation 22 | description: >- 23 | User documentation for the CATAPULT cmi5 Player, LMS Test Suite, and Content Test Suite. 24 | baseurl: "/CATAPULT" # the subpath of your site, e.g. /blog 25 | url: "https://adlnet.github.io" 26 | twitter_username: ADL_Initiative 27 | github_username: adlnet 28 | 29 | # Build settings 30 | theme: minima 31 | header_pages: 32 | - about.markdown 33 | 34 | plugins: 35 | - jekyll-feed 36 | 37 | # Exclude from processing. 38 | # The following items will not be processed, by default. 39 | # Any item listed under the `exclude:` key here will be automatically added to 40 | # the internal "default list". 41 | # 42 | # Excluded items can be processed by explicitly listing the directories or 43 | # their entries' file path in the `include:` list. 44 | # 45 | # exclude: 46 | # - .sass-cache/ 47 | # - .jekyll-cache/ 48 | # - gemfiles/ 49 | # - Gemfile 50 | # - Gemfile.lock 51 | # - node_modules/ 52 | # - vendor/bundle/ 53 | # - vendor/cache/ 54 | # - vendor/gems/ 55 | # - vendor/ruby/ 56 | -------------------------------------------------------------------------------- /docs/_includes/header.html: -------------------------------------------------------------------------------- 1 | 32 | -------------------------------------------------------------------------------- /docs/assets/main.scss: -------------------------------------------------------------------------------- 1 | --- 2 | # Overrides for some style rules in the minima theme that have WCAG 2.0 issues. 3 | --- 4 | 5 | $brand-color: #345d96; 6 | $grey-color: #71767a; 7 | 8 | @import "minima"; 9 | 10 | a { 11 | text-decoration: underline; 12 | } 13 | 14 | figure > img { 15 | border: 1px solid #c6cace; 16 | } 17 | -------------------------------------------------------------------------------- /docs/cts/img/au_card.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adlnet/CATAPULT/515126a016718d199abc008d027e89d1f9d8775f/docs/cts/img/au_card.png -------------------------------------------------------------------------------- /docs/cts/img/au_configuration.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adlnet/CATAPULT/515126a016718d199abc008d027e89d1f9d8775f/docs/cts/img/au_configuration.png -------------------------------------------------------------------------------- /docs/cts/img/au_session.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adlnet/CATAPULT/515126a016718d199abc008d027e89d1f9d8775f/docs/cts/img/au_session.png -------------------------------------------------------------------------------- /docs/cts/img/au_session_requirement_violated.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adlnet/CATAPULT/515126a016718d199abc008d027e89d1f9d8775f/docs/cts/img/au_session_requirement_violated.png -------------------------------------------------------------------------------- /docs/cts/img/course_details_no_tests.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adlnet/CATAPULT/515126a016718d199abc008d027e89d1f9d8775f/docs/cts/img/course_details_no_tests.png -------------------------------------------------------------------------------- /docs/cts/img/course_details_structure.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adlnet/CATAPULT/515126a016718d199abc008d027e89d1f9d8775f/docs/cts/img/course_details_structure.png -------------------------------------------------------------------------------- /docs/cts/img/course_details_tests.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adlnet/CATAPULT/515126a016718d199abc008d027e89d1f9d8775f/docs/cts/img/course_details_tests.png -------------------------------------------------------------------------------- /docs/cts/img/course_list.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adlnet/CATAPULT/515126a016718d199abc008d027e89d1f9d8775f/docs/cts/img/course_list.png -------------------------------------------------------------------------------- /docs/cts/img/delete_course.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adlnet/CATAPULT/515126a016718d199abc008d027e89d1f9d8775f/docs/cts/img/delete_course.png -------------------------------------------------------------------------------- /docs/cts/img/first_user.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adlnet/CATAPULT/515126a016718d199abc008d027e89d1f9d8775f/docs/cts/img/first_user.png -------------------------------------------------------------------------------- /docs/cts/img/navbar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adlnet/CATAPULT/515126a016718d199abc008d027e89d1f9d8775f/docs/cts/img/navbar.png -------------------------------------------------------------------------------- /docs/cts/img/new_course_error.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adlnet/CATAPULT/515126a016718d199abc008d027e89d1f9d8775f/docs/cts/img/new_course_error.png -------------------------------------------------------------------------------- /docs/cts/img/new_course_view.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adlnet/CATAPULT/515126a016718d199abc008d027e89d1f9d8775f/docs/cts/img/new_course_view.png -------------------------------------------------------------------------------- /docs/cts/img/new_test_registration.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adlnet/CATAPULT/515126a016718d199abc008d027e89d1f9d8775f/docs/cts/img/new_test_registration.png -------------------------------------------------------------------------------- /docs/cts/img/requirements.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adlnet/CATAPULT/515126a016718d199abc008d027e89d1f9d8775f/docs/cts/img/requirements.png -------------------------------------------------------------------------------- /docs/cts/img/sign_in.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adlnet/CATAPULT/515126a016718d199abc008d027e89d1f9d8775f/docs/cts/img/sign_in.png -------------------------------------------------------------------------------- /docs/cts/img/sign_out.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adlnet/CATAPULT/515126a016718d199abc008d027e89d1f9d8775f/docs/cts/img/sign_out.png -------------------------------------------------------------------------------- /docs/cts/img/test_agent_profile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adlnet/CATAPULT/515126a016718d199abc008d027e89d1f9d8775f/docs/cts/img/test_agent_profile.png -------------------------------------------------------------------------------- /docs/cts/img/test_details.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adlnet/CATAPULT/515126a016718d199abc008d027e89d1f9d8775f/docs/cts/img/test_details.png -------------------------------------------------------------------------------- /docs/cts/img/test_report.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adlnet/CATAPULT/515126a016718d199abc008d027e89d1f9d8775f/docs/cts/img/test_report.png -------------------------------------------------------------------------------- /docs/cts/img/test_results.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adlnet/CATAPULT/515126a016718d199abc008d027e89d1f9d8775f/docs/cts/img/test_results.png -------------------------------------------------------------------------------- /docs/cts/img/test_session_initial.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adlnet/CATAPULT/515126a016718d199abc008d027e89d1f9d8775f/docs/cts/img/test_session_initial.png -------------------------------------------------------------------------------- /docs/cts/img/test_session_passed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adlnet/CATAPULT/515126a016718d199abc008d027e89d1f9d8775f/docs/cts/img/test_session_passed.png -------------------------------------------------------------------------------- /docs/cts/img/xml_editor_error.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adlnet/CATAPULT/515126a016718d199abc008d027e89d1f9d8775f/docs/cts/img/xml_editor_error.png -------------------------------------------------------------------------------- /docs/index.markdown: -------------------------------------------------------------------------------- 1 | --- 2 | layout: home 3 | --- 4 | 19 | 20 | CATAPULT (cmi5 Advanced Testing Application and Player Underpinning Learning Technologies) is a project funded by ADL 21 | to develop tools and resources to support the adoption of cmi5 and xAPI across the Department of Defense (DoD) 22 | enterprise. 23 | 24 | * [About CATAPULT]({{ "/about" | relative_url }}) 25 | * [Project CATAPULT GitHub Repository](https://github.com/adlnet/CATAPULT) 26 | * [Project CATAPULT Course Examples](https://github.com/adlnet/CATAPULT/tree/main/course_examples) 27 | * [cmi5 Content Test Suite User Guide]({{ "/cts" | relative_url }}) 28 | * [cmi5 Content Test Suite README](https://github.com/adlnet/CATAPULT/blob/main/cts/README.md) 29 | * [cmi5 LMS Test Suite README](https://github.com/adlnet/CATAPULT/blob/main/lts/README.md) 30 | * [cmi5 Player User Guide]({{ "/player" | relative_url }}) 31 | * [cmi5 Player README](https://github.com/adlnet/CATAPULT/blob/main/player/README.md) 32 | 33 | ### Other Resources 34 | 35 | * [Course Examples](https://github.com/adlnet/CATAPULT/tree/main/course_examples) 36 | * [cmi5 Best Practices Guide](https://adlnet.gov/assets/uploads/cmi5%20Best%20Practices%20Guide%20-%20From%20Conception%20to%20Conformance.pdf) (PDF) 37 | * [cmi5 CATAPULT!](https://adlnet.gov/projects/cmi5-CATAPULT/) 38 | * [cmi5 Resources](https://www.adlnet.gov/resources/cmi5-resources/) from ADL Initiative 39 | * [cmi5 specification](https://aicc.github.io/CMI-5_Spec_Current/) 40 | * [xAPI specification](https://github.com/adlnet/xAPI-Spec) 41 | -------------------------------------------------------------------------------- /docs/player/img/api_doc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adlnet/CATAPULT/515126a016718d199abc008d027e89d1f9d8775f/docs/player/img/api_doc.png -------------------------------------------------------------------------------- /lts/.gitignore: -------------------------------------------------------------------------------- 1 | .env 2 | var 3 | lib/lms.custom.js 4 | -------------------------------------------------------------------------------- /lts/README.md: -------------------------------------------------------------------------------- 1 | ![Architecture Diagram](arch.png) 2 | 3 | # LMS Test Suite 4 | 5 | ## Building the Package Library 6 | 7 | This test suite is a CLI Node.js application. Install the test suite dependencies in the `lts/` directory by doing: 8 | 9 | npm ci 10 | 11 | Then build the LMS test packages by doing: 12 | 13 | cd pkg 14 | npx webpack 15 | 16 | ## Running Manually 17 | 18 | The test suite itself can then be executed manually by following the [documented test procedure (procedure.md)](procedure.md) using an LMS user interface and the package library. 19 | 20 | ## Running Automatically 21 | 22 | To run this suite automatically or via CI the LMS needs to provide a custom LMS implementation library script. The LMS script should be modeled after the example found at `lib/lms.catapult-player.js`. There is a template at `lib/lms.template.js` that can be copied and used as a starting point. Name the file `lib/lms.custom.js` to have it ignored in the repository by default. The template file is extensively commented to provide direction for implementers. 23 | 24 | Once the library script has been written, create a `.env` file in the directory pointed to its location. For example: 25 | 26 | # path to LMS script (required) 27 | CATAPULT_LMS="./lib/lms.catapult-player.js" 28 | 29 | # any values leveraged by the referenced script (optional) 30 | CATAPULT_PLAYER_API_URL="http://localhost:3398/api/v1/" 31 | CATAPULT_PLAYER_KEY="my-key" 32 | CATAPULT_PLAYER_SECRET="my-keys-secret" 33 | LRS_ENDPOINT="http://localhost:8081/catapult/lrs/" 34 | LRS_KEY="my-lrs-key" 35 | LRS_SECRET="my-lrs-secret" 36 | 37 | With those files in place the dependencies should be installed using: 38 | 39 | npm ci 40 | 41 | and then the tests can be run using: 42 | 43 | npx jest 44 | 45 | This will display the test output in the console and write a uniquely named JUnit formatted XML file to the `var/` directory. 46 | -------------------------------------------------------------------------------- /lts/arch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adlnet/CATAPULT/515126a016718d199abc008d027e89d1f9d8775f/lts/arch.png -------------------------------------------------------------------------------- /lts/jest-puppeteer.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | launch: { 3 | headless: process.env.HEADLESS !== "false", 4 | devtools: true 5 | } 6 | }; 7 | -------------------------------------------------------------------------------- /lts/jest.setup.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021 Rustici Software 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | require("expect-puppeteer"); 17 | 18 | if (! process.env.CATAPULT_LMS) { 19 | throw new Error("empty CATAPULT_LMS variable"); 20 | } 21 | 22 | try { 23 | global.LMS = require(process.env.CATAPULT_LMS); 24 | } 25 | catch (ex) { 26 | throw new Error(`Failed to load LMS script: ${ex}`); 27 | } 28 | 29 | expect.extend( 30 | { 31 | toContainObject (received, argument) { 32 | const pass = this.equals( 33 | received, 34 | expect.arrayContaining([ 35 | expect.objectContaining(argument) 36 | ]) 37 | ); 38 | 39 | if (pass) { 40 | return { 41 | message: () => (`expected ${this.utils.printReceived(received)} not to contain object ${this.utils.printExpected(argument)}`), 42 | pass: true 43 | }; 44 | } 45 | else { 46 | return { 47 | message: () => (`expected ${this.utils.printReceived(received)} to contain object ${this.utils.printExpected(argument)}`), 48 | pass: false 49 | }; 50 | } 51 | } 52 | } 53 | ); 54 | -------------------------------------------------------------------------------- /lts/lib/errors.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021 Rustici Software 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | class InvalidPackageError extends Error {}; 17 | Object.defineProperty( 18 | InvalidPackageError.prototype, 19 | "name", 20 | { 21 | value: InvalidPackageError.name 22 | } 23 | ); 24 | 25 | module.exports = { 26 | InvalidPackageError 27 | }; 28 | -------------------------------------------------------------------------------- /lts/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "catapult-lts", 3 | "version": "1.0.0", 4 | "description": "Catapult LMS testing system", 5 | "repository": { 6 | "type": "git", 7 | "url": "git+https://github.com/adlnet/CATAPULT.git" 8 | }, 9 | "scripts": { 10 | "test": "jest" 11 | }, 12 | "keywords": [ 13 | "cmi5" 14 | ], 15 | "author": "Rustici Software ", 16 | "license": "Apache-2.0", 17 | "dependencies": { 18 | "@cmi5/requirements": "^1.0.0", 19 | "@hapi/wreck": "^17.1.0", 20 | "dotenv": "^10.0.0", 21 | "jest": "^27.0.6", 22 | "jest-extended": "^0.11.5", 23 | "jest-junit": "^12.2.0", 24 | "jest-puppeteer": "^5.0.4", 25 | "puppeteer": "^10.1.0" 26 | }, 27 | "devDependencies": { 28 | "@rusticisoftware/cmi5": "^3.0.0", 29 | "copy-webpack-plugin": "^9.0.1", 30 | "eslint": "^7.31.0", 31 | "eslint-webpack-plugin": "^2.5.4", 32 | "html-webpack-plugin": "^5.3.2", 33 | "webpack": "^5.45.1", 34 | "webpack-cli": "^4.7.2", 35 | "zip-webpack-plugin": "^4.0.1" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /lts/pkg/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "es2020": true 5 | }, 6 | "parserOptions": { 7 | "sourceType": "module" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /lts/pkg/src/001-essentials/cmi5.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 20 | <langstring lang="en">CATAPULT LMS Test Course: 001 Essentials</langstring> 21 | 22 | 23 | CATAPULT LMS Test Course: 001 Essentials 24 | 25 | 26 | 29 | 30 | <langstring lang="en">CATAPULT LMS Test Block: 001 Essentials</langstring> 31 | 32 | 33 | CATAPULT LMS Test Block: 001 Essentials 34 | 35 | 41 | 42 | <langstring lang="en">CATAPULT LMS Test AU: 001 Essentials</langstring> 43 | 44 | 45 | CATAPULT LMS Test AU: 001 Essentials 46 | 47 | 48 | 51 | 52 | 53 | sample string 54 | 55 | 56 | sample value 57 | 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /lts/pkg/src/002-allowed/002-allowed.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021 Rustici Software 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | import Helpers from "../lib/helpers"; 17 | 18 | const execute = async () => { 19 | const cmi5 = await Helpers.initAU(); 20 | 21 | if (! cmi5) { 22 | return; 23 | } 24 | 25 | const allowedSt = cmi5.prepareStatement("http://adlnet.gov/expapi/verbs/experienced"); 26 | 27 | try { 28 | await cmi5.sendStatement(allowedSt); 29 | } 30 | catch (ex) { 31 | Helpers.storeResult(false, false, {reqId: "9.3.0.0-9", msg: `Failed to send allowed statement: ${ex}`}); 32 | 33 | return; 34 | } 35 | 36 | await Helpers.closeAU(cmi5); 37 | 38 | Helpers.storeResult( 39 | true, 40 | false, 41 | { 42 | registration: cmi5.getRegistration(), 43 | msg: "Sending allowed statement(s) validated" 44 | } 45 | ); 46 | }; 47 | 48 | execute(); 49 | -------------------------------------------------------------------------------- /lts/pkg/src/002-allowed/cmi5.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 20 | <langstring lang="en">CATAPULT LMS Test Course: 002 Allowed</langstring> 21 | 22 | 23 | CATAPULT LMS Test Course: 002 Allowed 24 | 25 | 26 | 30 | 31 | <langstring lang="en">CATAPULT LMS Test AU: 002 Allowed</langstring> 32 | 33 | 34 | CATAPULT LMS Test AU: 002 Allowed 35 | 36 | index.html 37 | 38 | 39 | -------------------------------------------------------------------------------- /lts/pkg/src/003-launchMethod-OwnWindow/003-launchMethod-OwnWindow.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021 Rustici Software 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | import Helpers from "../lib/helpers"; 17 | 18 | const execute = async () => { 19 | const cmi5 = await Helpers.initAU(); 20 | 21 | if (! cmi5) { 22 | return; 23 | } 24 | 25 | if (window.self !== window.top) { 26 | Helpers.storeResult(false, false, {reqId: "8.1.0.0-2", msg: "window.self does not equal window.top, not in its own window?"}); 27 | 28 | return; 29 | } 30 | 31 | await Helpers.closeAU(cmi5); 32 | 33 | Helpers.storeResult( 34 | true, 35 | false, 36 | { 37 | registration: cmi5.getRegistration(), 38 | msg: "AU launched in its own window validated" 39 | } 40 | ); 41 | }; 42 | 43 | execute(); 44 | -------------------------------------------------------------------------------- /lts/pkg/src/003-launchMethod-OwnWindow/cmi5.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 20 | <langstring lang="en">CATAPULT LMS Test Course: 003 launchMethod OwnWindow</langstring> 21 | 22 | 23 | CATAPULT LMS Test Course: 003 launchMethod OwnWindow 24 | 25 | 26 | 31 | 32 | <langstring lang="en">CATAPULT LMS Test AU: 003 launchMethod OwnWindow</langstring> 33 | 34 | 35 | CATAPULT LMS Test AU: 003 launchMethod OwnWindow 36 | 37 | index.html 38 | 39 | 40 | -------------------------------------------------------------------------------- /lts/pkg/src/004-1-moveOn-Completed/004-1-moveOn-Completed.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021 Rustici Software 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | import Helpers from "../lib/helpers"; 17 | 18 | const execute = async () => { 19 | const cmi5 = await Helpers.initAU(); 20 | 21 | if (! cmi5) { 22 | return; 23 | } 24 | 25 | const moveOn = cmi5.getMoveOn(); 26 | 27 | if (moveOn !== "Completed") { 28 | Helpers.storeResult(false, false, {msg: `Unexpected moveOn, expected "Completed", received: ${moveOn}`}); 29 | 30 | return; 31 | } 32 | 33 | try { 34 | await cmi5.completed(); 35 | } 36 | catch (ex) { 37 | Helpers.storeResult(false, true, {msg: `Failed call to completed: ${ex}`}); 38 | 39 | return; 40 | } 41 | 42 | await Helpers.closeAU(cmi5); 43 | 44 | Helpers.storeResult( 45 | true, 46 | false, 47 | { 48 | registration: cmi5.getRegistration(), 49 | msg: "Completed statement for AU with Completed moveOn" 50 | } 51 | ); 52 | }; 53 | 54 | execute(); 55 | -------------------------------------------------------------------------------- /lts/pkg/src/004-1-moveOn-Completed/cmi5.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 20 | <langstring lang="en">CATAPULT LMS Test Course: 004-1 moveOn Completed</langstring> 21 | 22 | 23 | CATAPULT LMS Test Course: 004-1 moveOn Completed 24 | 25 | 26 | 27 | 28 | <langstring lang="en">CATAPULT LMS Test Block: 004-1 moveOn Completed</langstring> 29 | 30 | 31 | CATAPULT LMS Test Block: 004-1 moveOn Completed 32 | 33 | 37 | 38 | <langstring lang="en">CATAPULT LMS Test AU: 004-1 moveOn Completed</langstring> 39 | 40 | 41 | CATAPULT LMS Test AU: 004-1 moveOn Completed 42 | 43 | index.html 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /lts/pkg/src/004-2-moveOn-CompletedOrPassed/004-2-moveOn-CompletedOrPassed.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021 Rustici Software 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | import Helpers from "../lib/helpers"; 17 | 18 | const execute = async () => { 19 | const cmi5 = await Helpers.initAU(); 20 | 21 | if (! cmi5) { 22 | return; 23 | } 24 | 25 | const moveOn = cmi5.getMoveOn(); 26 | 27 | if (moveOn !== "CompletedOrPassed") { 28 | Helpers.storeResult(false, false, {msg: `Unexpected moveOn, expected "CompletedOrPassed", received: ${moveOn}`}); 29 | 30 | return; 31 | } 32 | 33 | try { 34 | await cmi5.completed(); 35 | } 36 | catch (ex) { 37 | Helpers.storeResult(false, true, {msg: `Failed call to completed: ${ex}`}); 38 | 39 | return; 40 | } 41 | 42 | await Helpers.closeAU(cmi5); 43 | 44 | Helpers.storeResult( 45 | true, 46 | false, 47 | { 48 | registration: cmi5.getRegistration(), 49 | msg: "Completed statement for AU with CompletedOrPassed moveOn" 50 | } 51 | ); 52 | }; 53 | 54 | execute(); 55 | -------------------------------------------------------------------------------- /lts/pkg/src/004-2-moveOn-CompletedOrPassed/cmi5.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 20 | <langstring lang="en">CATAPULT LMS Test Course: 004-2 moveOn CompletedOrPassed</langstring> 21 | 22 | 23 | CATAPULT LMS Test Course: 004-2 moveOn CompletedOrPassed 24 | 25 | 26 | 27 | 28 | <langstring lang="en">CATAPULT LMS Test Block: 004-2 moveOn CompletedOrPassed</langstring> 29 | 30 | 31 | CATAPULT LMS Test Block: 004-2 moveOn CompletedOrPassed 32 | 33 | 37 | 38 | <langstring lang="en">CATAPULT LMS Test AU: 004-2 moveOn CompletedOrPassed</langstring> 39 | 40 | 41 | CATAPULT LMS Test AU: 004-2 moveOn CompletedOrPassed 42 | 43 | index.html 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /lts/pkg/src/004-3-moveOn-Passed/004-3-moveOn-Passed.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021 Rustici Software 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | import Helpers from "../lib/helpers"; 17 | 18 | const execute = async () => { 19 | const cmi5 = await Helpers.initAU(); 20 | 21 | if (! cmi5) { 22 | return; 23 | } 24 | 25 | const moveOn = cmi5.getMoveOn(); 26 | 27 | if (moveOn !== "Passed") { 28 | Helpers.storeResult(false, false, {msg: `Unexpected moveOn, expected "Passed", received: ${moveOn}`}); 29 | 30 | return; 31 | } 32 | 33 | try { 34 | await cmi5.passed(); 35 | } 36 | catch (ex) { 37 | Helpers.storeResult(false, true, {msg: `Failed call to passed: ${ex}`}); 38 | 39 | return; 40 | } 41 | 42 | await Helpers.closeAU(cmi5); 43 | 44 | Helpers.storeResult( 45 | true, 46 | false, 47 | { 48 | registration: cmi5.getRegistration(), 49 | msg: "Passed stateement for AU with Passed moveOn" 50 | } 51 | ); 52 | }; 53 | 54 | execute(); 55 | -------------------------------------------------------------------------------- /lts/pkg/src/004-3-moveOn-Passed/cmi5.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 20 | <langstring lang="en">CATAPULT LMS Test Course: 004-3 moveOn Passed</langstring> 21 | 22 | 23 | CATAPULT LMS Test Course: 004-3 moveOn Passed 24 | 25 | 26 | 27 | 28 | <langstring lang="en">CATAPULT LMS Test Block: 004-3 moveOn Passed</langstring> 29 | 30 | 31 | CATAPULT LMS Test Block: 004-3 moveOn Passed 32 | 33 | 37 | 38 | <langstring lang="en">CATAPULT LMS Test AU: 004-3 moveOn Passed</langstring> 39 | 40 | 41 | CATAPULT LMS Test AU: 004-3 moveOn Passed 42 | 43 | index.html 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /lts/pkg/src/004-4-moveOn-CompletedOrPassed/004-4-moveOn-CompletedOrPassed.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021 Rustici Software 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | import Helpers from "../lib/helpers"; 17 | 18 | const execute = async () => { 19 | const cmi5 = await Helpers.initAU(); 20 | 21 | if (! cmi5) { 22 | return; 23 | } 24 | 25 | const moveOn = cmi5.getMoveOn(); 26 | 27 | if (moveOn !== "CompletedOrPassed") { 28 | Helpers.storeResult(false, false, {msg: `Unexpected moveOn, expected "CompletedOrPassed", received: ${moveOn}`}); 29 | 30 | return; 31 | } 32 | 33 | try { 34 | await cmi5.passed(); 35 | } 36 | catch (ex) { 37 | Helpers.storeResult(false, true, {msg: `Failed call to passed: ${ex}`}); 38 | 39 | return; 40 | } 41 | 42 | await Helpers.closeAU(cmi5); 43 | 44 | Helpers.storeResult( 45 | true, 46 | false, 47 | { 48 | registration: cmi5.getRegistration(), 49 | msg: "Passed stateement for AU with CompletedOrPassed moveOn" 50 | } 51 | ); 52 | }; 53 | 54 | execute(); 55 | -------------------------------------------------------------------------------- /lts/pkg/src/004-4-moveOn-CompletedOrPassed/cmi5.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 20 | <langstring lang="en">CATAPULT LMS Test Course: 004-4 moveOn CompletedOrPassed</langstring> 21 | 22 | 23 | CATAPULT LMS Test Course: 004-4 moveOn CompletedOrPassed 24 | 25 | 26 | 27 | 28 | <langstring lang="en">CATAPULT LMS Test Block: 004-4 moveOn CompletedOrPassed</langstring> 29 | 30 | 31 | CATAPULT LMS Test Block: 004-4 moveOn CompletedOrPassed 32 | 33 | 37 | 38 | <langstring lang="en">CATAPULT LMS Test AU: 004-4 moveOn CompletedOrPassed</langstring> 39 | 40 | 41 | CATAPULT LMS Test AU: 004-4 moveOn CompletedOrPassed 42 | 43 | index.html 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /lts/pkg/src/004-5-moveOn-NotApplicable/004-5-moveOn-NotApplicable.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021 Rustici Software 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | import Helpers from "../lib/helpers"; 17 | 18 | const execute = async () => { 19 | // 20 | // this AU is launched just to make sure a registration is generated 21 | // but the AU itself should have already been satisfied because of 22 | // the NotApplicable moveOn which must be handled at registration 23 | // creation 24 | // 25 | const cmi5 = await Helpers.initAU(); 26 | 27 | if (! cmi5) { 28 | return; 29 | } 30 | 31 | await Helpers.closeAU(cmi5); 32 | 33 | Helpers.storeResult( 34 | true, 35 | false, 36 | { 37 | actor: cmi5.getActor(), 38 | registration: cmi5.getRegistration(), 39 | activityId: cmi5.getActivityId(), 40 | msg: "Session start/end for NotApplicable moveOn" 41 | } 42 | ); 43 | }; 44 | 45 | execute(); 46 | -------------------------------------------------------------------------------- /lts/pkg/src/004-5-moveOn-NotApplicable/cmi5.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 20 | <langstring lang="en">CATAPULT LMS Test Course: 004-5 moveOn NotApplicable</langstring> 21 | 22 | 23 | CATAPULT LMS Test Course: 004-5 moveOn NotApplicable 24 | 25 | 26 | 27 | 28 | <langstring lang="en">CATAPULT LMS Test Block: 004-5 moveOn NotApplicable</langstring> 29 | 30 | 31 | CATAPULT LMS Test Block: 004-5 moveOn NotApplicable 32 | 33 | 37 | 38 | <langstring lang="en">CATAPULT LMS Test AU: 004-5 moveOn NotApplicable</langstring> 39 | 40 | 41 | CATAPULT LMS Test AU: 004-5 moveOn NotApplicable 42 | 43 | index.html 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /lts/pkg/src/005-1-invalid-au/cmi5.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 20 | <langstring lang="en">CATAPULT LMS Test Course: 005-1 Invalid AU</langstring> 21 | 22 | 23 | CATAPULT LMS Test Course: 005-1 Invalid AU 24 | 25 | 26 | 31 | 32 | <langstring lang="en">CATAPULT LMS Test AU: 005-1 Invalid AU</langstring> 33 | 34 | 35 | CATAPULT LMS Test AU: 005-1 Invalid AU 36 | 37 | index.html 38 | 39 | 40 | -------------------------------------------------------------------------------- /lts/pkg/src/005-2-invalid-au/005-2-invalid-au.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021 Rustici Software 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | import Cmi5 from "@rusticisoftware/cmi5"; 17 | import Helpers from "../lib/helpers"; 18 | 19 | const execute = async () => { 20 | const cmi5 = await Helpers.initAU(); 21 | 22 | if (! cmi5) { 23 | return; 24 | } 25 | 26 | // 27 | // create a set of statement templates that should be valid so that they can be used as is 28 | // or by altering them in some way to make them invalid to check that they are rejected 29 | // 30 | const stTemplates = { 31 | passed: JSON.stringify( 32 | cmi5.passedStatement() 33 | ), 34 | failed: JSON.stringify( 35 | cmi5.failedStatement() 36 | ) 37 | }; 38 | 39 | for (const stDef of [ 40 | { 41 | // failed verb sent to succeed 42 | type: "failed", 43 | cfg: { 44 | shouldSucceed: true 45 | } 46 | }, 47 | { 48 | // failed verb after failed has already occurred same session 49 | // Alo validates part of 9.3.0.0-2 (d) 50 | reqId: "9.3.0.0-2 (d)", 51 | type: "failed" 52 | }, 53 | { 54 | // passed verb after failed has already occurred 55 | // Alo validates part of 9.3.0.0-2 (d) 56 | reqId: "9.3.0.0-3 (d)", 57 | type: "passed" 58 | } 59 | ]) { 60 | const st = JSON.parse(stTemplates[stDef.type]); 61 | 62 | // using the template in the way that this is doing so means there would be 63 | // the same statement id with all statements of a kind so generate a new one 64 | st.id = Cmi5.uuidv4(); 65 | 66 | // eslint-disable-next-line no-await-in-loop 67 | if (! await Helpers.sendStatement(cmi5, st, stDef.reqId, stDef.cfg)) { 68 | return; 69 | } 70 | } 71 | 72 | await Helpers.closeAU(cmi5); 73 | 74 | Helpers.storeResult(true, false, {msg: "LMS rejected set of statements with 403 response status"}); 75 | }; 76 | 77 | execute(); 78 | -------------------------------------------------------------------------------- /lts/pkg/src/005-2-invalid-au/cmi5.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 20 | <langstring lang="en">CATAPULT LMS Test Course: 005-2 Invalid AU</langstring> 21 | 22 | 23 | CATAPULT LMS Test Course: 005-2 Invalid AU 24 | 25 | 26 | 31 | 32 | <langstring lang="en">CATAPULT LMS Test AU: 005-2 Invalid AU</langstring> 33 | 34 | 35 | CATAPULT LMS Test AU: 005-2 Invalid AU 36 | 37 | index.html 38 | 39 | 40 | -------------------------------------------------------------------------------- /lts/pkg/src/006-launchMode/006-launchMode.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021 Rustici Software 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | import Helpers from "../lib/helpers"; 17 | 18 | const execute = async () => { 19 | const cmi5 = await Helpers.initAU(); 20 | 21 | if (! cmi5) { 22 | return; 23 | } 24 | 25 | // 26 | // this AU is intended to be run with a launchMode of Browse or 27 | // Review such that when it is run the completed, passed, and 28 | // failed statements are expected to be rejected where a 29 | // non-rejection is a failed test 30 | // 31 | if (cmi5.getLaunchMode() !== "Browse" && cmi5.getLaunchMode() !== "Review") { 32 | Helpers.storeResult(false, true, {msg: `Expected launchMode to be Browse or Review but received: ${cmi5.getLaunchMode()}`}); 33 | 34 | return; 35 | } 36 | 37 | // 38 | // Also validates: 10.2.2.0-5 (d) 39 | // 40 | const reqId = cmi5.getLaunchMode() === "Browse" ? "10.2.2.0-2 (d)" : "10.2.2.0-3 (d)"; 41 | 42 | if (! await Helpers.sendStatement(cmi5, cmi5.completedStatement(), reqId)) { 43 | return; 44 | } 45 | if (! await Helpers.sendStatement(cmi5, cmi5.failedStatement(), reqId)) { 46 | return; 47 | } 48 | if (! await Helpers.sendStatement(cmi5, cmi5.passedStatement(), reqId)) { 49 | return; 50 | } 51 | 52 | Helpers.storeResult(true, false, {msg: "LMS rejected set of statements with 403 response status"}); 53 | 54 | await Helpers.closeAU(cmi5); 55 | }; 56 | 57 | execute(); 58 | -------------------------------------------------------------------------------- /lts/pkg/src/006-launchMode/cmi5.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 20 | <langstring lang="en">CATAPULT LMS Test Course: 006 launchMode</langstring> 21 | 22 | 23 | CATAPULT LMS Test Course: 006 launchMode 24 | 25 | 26 | 30 | 31 | <langstring lang="en">CATAPULT LMS Test AU: 006 launchMode</langstring> 32 | 33 | 34 | CATAPULT LMS Test AU: 006 launchMode 35 | 36 | index.html 37 | 38 | 39 | -------------------------------------------------------------------------------- /lts/pkg/src/007-1-multi-session/007-1-multi-session.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021 Rustici Software 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | import Helpers from "../lib/helpers"; 17 | 18 | const execute = async () => { 19 | const cmi5 = await Helpers.initAU(); 20 | 21 | if (! cmi5) { 22 | return; 23 | } 24 | 25 | // 26 | // this AU is intended to be run multiple times, at least twice, 27 | // such that the first time it is run it will capture a completed 28 | // statement, then store in the State that it has already been 29 | // run, so that the second (and after) time it will try to 30 | // capture another completed statement which is then expected 31 | // to be rejected where a non-rejection is a failed test 32 | // 33 | const stateContent = await Helpers.stateRequest(cmi5, "get", "007-1-multi-session"); 34 | 35 | if (typeof stateContent === "undefined") { 36 | // 37 | // first run 38 | // 39 | if (! await Helpers.sendStatement(cmi5, cmi5.completedStatement(), "9.3.0.0-6 (d1)", {shouldSucceed: true})) { 40 | return; 41 | } 42 | 43 | if (! await Helpers.stateRequest(cmi5, "put", "007-1-multi-session", JSON.stringify({firstRun: true}))) { 44 | return; 45 | } 46 | 47 | Helpers.storeResult(true, false, {registration: cmi5.getRegistration(), msg: "First stage completed successfully"}); 48 | } 49 | else { 50 | // 51 | // subsequent run 52 | // 53 | if (! await Helpers.sendStatement(cmi5, cmi5.completedStatement(), "9.3.0.0-6 (d2)")) { 54 | return; 55 | } 56 | 57 | Helpers.storeResult(true, false, {msg: "LMS rejected set of statements with 403 response status"}); 58 | } 59 | 60 | await Helpers.closeAU(cmi5); 61 | }; 62 | 63 | execute(); 64 | -------------------------------------------------------------------------------- /lts/pkg/src/007-1-multi-session/cmi5.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 20 | <langstring lang="en">CATAPULT LMS Test Course: 007-1 Multi Session</langstring> 21 | 22 | 23 | CATAPULT LMS Test Course: 007-1 Multi Session 24 | 25 | 26 | 30 | 31 | <langstring lang="en">CATAPULT LMS Test AU: 007-1 Multi Session</langstring> 32 | 33 | 34 | CATAPULT LMS Test AU: 007-1 Multi Session 35 | 36 | index.html 37 | 38 | 39 | -------------------------------------------------------------------------------- /lts/pkg/src/007-2-multi-session/007-2-multi-session.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021 Rustici Software 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | import Helpers from "../lib/helpers"; 17 | 18 | const execute = async () => { 19 | const cmi5 = await Helpers.initAU(); 20 | 21 | if (! cmi5) { 22 | return; 23 | } 24 | 25 | // 26 | // this AU is intended to be run multiple times, at least twice, 27 | // such that the first time it is run it will capture a passed 28 | // statement, then store in the State that it has already been 29 | // run, so that the second (and after) time it will try to 30 | // capture another passed/failed statement which is then expected 31 | // to be rejected where a non-rejection is a failed test 32 | // 33 | const stateContent = await Helpers.stateRequest(cmi5, "get", "007-2-multi-session"); 34 | 35 | if (typeof stateContent === "undefined") { 36 | // 37 | // first run 38 | // 39 | if (! await Helpers.sendStatement(cmi5, cmi5.passedStatement(), "9.3.0.0-7 (d1)", {shouldSucceed: true})) { 40 | return; 41 | } 42 | 43 | if (! await Helpers.stateRequest(cmi5, "put", "007-2-multi-session", JSON.stringify({firstRun: true}))) { 44 | return; 45 | } 46 | 47 | await Helpers.closeAU(cmi5); 48 | 49 | Helpers.storeResult(true, false, {registration: cmi5.getRegistration(), msg: "First stage completed successfully"}); 50 | } 51 | else { 52 | // 53 | // subsequent run 54 | // 55 | if (! await Helpers.sendStatement(cmi5, cmi5.passedStatement(), "9.3.0.0-7 (d2)")) { 56 | return; 57 | } 58 | if (! await Helpers.sendStatement(cmi5, cmi5.failedStatement(), "9.3.0.0-8 (d)")) { 59 | return; 60 | } 61 | 62 | await Helpers.closeAU(cmi5); 63 | 64 | Helpers.storeResult(true, false, {msg: "LMS rejected set of statements with 403 response status"}); 65 | } 66 | }; 67 | 68 | execute(); 69 | -------------------------------------------------------------------------------- /lts/pkg/src/007-2-multi-session/cmi5.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 20 | <langstring lang="en">CATAPULT LMS Test Course: 007-2 Multi Session</langstring> 21 | 22 | 23 | CATAPULT LMS Test Course: 007-2 Multi Session 24 | 25 | 26 | 30 | 31 | <langstring lang="en">CATAPULT LMS Test AU: 007-2 Multi Session</langstring> 32 | 33 | 34 | CATAPULT LMS Test AU: 007-2 Multi Session 35 | 36 | index.html 37 | 38 | 39 | -------------------------------------------------------------------------------- /lts/pkg/src/008-1-abandoned/cmi5.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 20 | <langstring lang="en">CATAPULT LMS Test Course: 008-1 Abandoned</langstring> 21 | 22 | 23 | CATAPULT LMS Test Course: 008-1 Abandoned 24 | 25 | 26 | 30 | 31 | <langstring lang="en">CATAPULT LMS Test AU: 008-1 Abandoned</langstring> 32 | 33 | 34 | CATAPULT LMS Test AU: 008-1 Abandoned 35 | 36 | index.html 37 | 38 | 39 | -------------------------------------------------------------------------------- /lts/pkg/src/009-1-waived/009-1-waived.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021 Rustici Software 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | import Helpers from "../lib/helpers"; 17 | 18 | const execute = async () => { 19 | const cmi5 = await Helpers.initAU(); 20 | 21 | if (! cmi5) { 22 | return; 23 | } 24 | 25 | await Helpers.closeAU(cmi5); 26 | 27 | Helpers.storeResult( 28 | true, 29 | false, 30 | { 31 | session: cmi5.getSessionId(), 32 | registration: cmi5.getRegistration(), 33 | actor: cmi5.getActor(), 34 | activityId: cmi5.getActivityId(), 35 | msg: "completed successfully" 36 | } 37 | ); 38 | }; 39 | 40 | execute(); 41 | -------------------------------------------------------------------------------- /lts/pkg/src/009-1-waived/cmi5.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 20 | <langstring lang="en">CATAPULT LMS Test Course: 009-1 Waived</langstring> 21 | 22 | 23 | CATAPULT LMS Test Course: 009-1 Waived 24 | 25 | 26 | 30 | 31 | <langstring lang="en">CATAPULT LMS Test AU: 009-1 Waived 0</langstring> 32 | 33 | 34 | CATAPULT LMS Test AU: 009-1 Waived 0 35 | 36 | index.html 37 | 38 | 39 | -------------------------------------------------------------------------------- /lts/pkg/src/102-zip64/cmi5.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 20 | <langstring lang="en">CATAPULT LMS Test Course: 102 Zip64</langstring> 21 | 22 | 23 | CATAPULT LMS Test Course: 102 Zip64 24 | 25 | 26 | 30 | 31 | <langstring lang="en">CATAPULT LMS Test AU: 102 Zip64</langstring> 32 | 33 | 34 | CATAPULT LMS Test AU: 102 Zip64 35 | 36 | index.html 37 | 38 | 39 | -------------------------------------------------------------------------------- /lts/pkg/src/201-1-iris-course-id.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 20 | 21 | <langstring lang="en">CATAPULT LMS Test Course: 201-1-iris-course-id</langstring> 22 | 23 | 24 | CATAPULT LMS Test Course: 201-1-iris-course-id 25 | 26 | 27 | 28 | 29 | <langstring lang="en">CATAPULT LMS Test AU: 201-1-iris-course-id</langstring> 30 | 31 | 32 | CATAPULT LMS Test AU: 201-1-iris-course-id 33 | 34 | index.html 35 | 36 | 37 | -------------------------------------------------------------------------------- /lts/pkg/src/201-2-iris-block-id.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 20 | <langstring lang="en">CATAPULT LMS Test Course: 201-2-iris-block-id</langstring> 21 | 22 | 23 | CATAPULT LMS Test Course: 201-2-iris-block-id 24 | 25 | 26 | 27 | 28 | 29 | <langstring lang="en">CATAPULT LMS Test Block: 201-2-iris-block-id</langstring> 30 | 31 | 32 | CATAPULT LMS Test Block: 201-2-iris-block-id 33 | 34 | 35 | 36 | <langstring lang="en">CATAPULT LMS Test AU: 201-2-iris-block-id</langstring> 37 | 38 | 39 | CATAPULT LMS Test AU: 201-2-iris-block-id 40 | 41 | index.html 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /lts/pkg/src/201-3-iris-au-id.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 20 | <langstring lang="en">CATAPULT LMS Test Course: 201-3-iris-au-id</langstring> 21 | 22 | 23 | CATAPULT LMS Test Course: 201-3-iris-au-id 24 | 25 | 26 | 27 | 28 | 29 | <langstring lang="en">CATAPULT LMS Test AU: 201-3-iris-au-id</langstring> 30 | 31 | 32 | CATAPULT LMS Test AU: 201-3-iris-au-id 33 | 34 | index.html 35 | 36 | 37 | -------------------------------------------------------------------------------- /lts/pkg/src/201-4-iris-objective-id.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 20 | <langstring lang="en">CATAPULT LMS Test Course: 201-4-iris-objective-id</langstring> 21 | 22 | 23 | CATAPULT LMS Test Course: 201-4-iris-objective-id 24 | 25 | 26 | 27 | 28 | 29 | 30 | <langstring lang="en">CATAPULT LMS Test Objective: 201-4-iris-objective-id</langstring> 31 | 32 | 33 | CATAPULT LMS Test Objective: 201-4-iris-objective-id 34 | 35 | 36 | 37 | 38 | 39 | <langstring lang="en">CATAPULT LMS Test AU: 201-4-iris-objective-id</langstring> 40 | 41 | 42 | CATAPULT LMS Test AU: 201-4-iris-objective-id 43 | 44 | 45 | 46 | 47 | index.html 48 | 49 | 50 | -------------------------------------------------------------------------------- /lts/pkg/src/202-1-relative-url-no-zip.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 20 | <langstring lang="en">CATAPULT LMS Test Course: 202-1-relative-url-no-zip</langstring> 21 | 22 | 23 | CATAPULT LMS Test Course: 202-1-relative-url-no-zip 24 | 25 | 26 | 27 | 28 | <langstring lang="en">CATAPULT LMS Test AU: 202-1-relative-url-no-zip</langstring> 29 | 30 | 31 | CATAPULT LMS Test AU: 202-1-relative-url-no-zip 32 | 33 | 34 | index.html 35 | 36 | 37 | -------------------------------------------------------------------------------- /lts/pkg/src/202-2-relative-url-no-zip.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 20 | <langstring lang="en">CATAPULT LMS Test Course: 202-2-relative-url-no-zip</langstring> 21 | 22 | 23 | CATAPULT LMS Test Course: 202-2-relative-url-no-zip 24 | 25 | 26 | 27 | 28 | <langstring lang="en">CATAPULT LMS Test AU: 202-2-relative-url-no-zip</langstring> 29 | 30 | 31 | CATAPULT LMS Test AU: 202-2-relative-url-no-zip 32 | 33 | 34 | path/1/index.html 35 | 36 | 37 | -------------------------------------------------------------------------------- /lts/pkg/src/202-3-relative-url-no-zip.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 20 | <langstring lang="en">CATAPULT LMS Test Course: 202-3-relative-url-no-zip</langstring> 21 | 22 | 23 | CATAPULT LMS Test Course: 202-3-relative-url-no-zip 24 | 25 | 26 | 27 | 28 | <langstring lang="en">CATAPULT LMS Test AU: 202-3-relative-url-no-zip</langstring> 29 | 30 | 31 | CATAPULT LMS Test AU: 202-3-relative-url-no-zip 32 | 33 | 34 | index.html?abc=def 35 | 36 | 37 | -------------------------------------------------------------------------------- /lts/pkg/src/202-4-relative-url-no-zip.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 20 | <langstring lang="en">CATAPULT LMS Test Course: 202-4-relative-url-no-zip</langstring> 21 | 22 | 23 | CATAPULT LMS Test Course: 202-4-relative-url-no-zip 24 | 25 | 26 | 27 | 28 | <langstring lang="en">CATAPULT LMS Test AU: 202-4-relative-url-no-zip</langstring> 29 | 30 | 31 | CATAPULT LMS Test AU: 202-4-relative-url-no-zip 32 | 33 | 34 | path/1/index.html?abc=def 35 | 36 | 37 | -------------------------------------------------------------------------------- /lts/pkg/src/202-5-relative-url-no-zip.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 20 | <langstring lang="en">CATAPULT LMS Test Course: 202-5-relative-url-no-zip</langstring> 21 | 22 | 23 | CATAPULT LMS Test Course: 202-5-relative-url-no-zip 24 | 25 | 26 | 27 | 28 | <langstring lang="en">CATAPULT LMS Test AU: 202-5-relative-url-no-zip</langstring> 29 | 30 | 31 | CATAPULT LMS Test AU: 202-5-relative-url-no-zip 32 | 33 | 34 | /index.html 35 | 36 | 37 | -------------------------------------------------------------------------------- /lts/pkg/src/203-1-relative-url-no-reference/README.md: -------------------------------------------------------------------------------- 1 | # LMS Test Package 2 | 3 | Intentionally invalid package, cmi5.xml with AU with relative URL but no corresponding file in the zip. 4 | 5 | Used to validate: 14.1.0.0-4 6 | -------------------------------------------------------------------------------- /lts/pkg/src/203-1-relative-url-no-reference/cmi5.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 20 | <langstring lang="en">CATAPULT LMS Test Course: 203-1-relative-url-no-reference</langstring> 21 | 22 | 23 | CATAPULT LMS Test Course: 203-1-relative-url-no-reference 24 | 25 | 26 | 27 | 28 | <langstring lang="en">CATAPULT LMS Test AU: 203-1-relative-url-no-reference</langstring> 29 | 30 | 31 | CATAPULT LMS Test AU: 203-1-relative-url-no-reference 32 | 33 | 34 | not-found.html 35 | 36 | 37 | -------------------------------------------------------------------------------- /lts/pkg/src/204-query-string-conflict-endpoint.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 20 | <langstring lang="en">CATAPULT LMS Test Course: 204-query-string-conflict-endpoint</langstring> 21 | 22 | 23 | CATAPULT LMS Test Course: 204-query-string-conflict-endpoint 24 | 25 | 26 | 27 | 28 | <langstring lang="en">CATAPULT LMS Test AU: 204-query-string-conflict-endpoint</langstring> 29 | 30 | 31 | CATAPULT LMS Test AU: 204-query-string-conflict-endpoint 32 | 33 | 34 | index.html?endpoint=http://example.org/lrs 35 | 36 | 37 | -------------------------------------------------------------------------------- /lts/pkg/src/205-3-duplicated-au.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 20 | <langstring lang="en">CATAPULT LMS Test Course: 205-3-duplicated-au</langstring> 21 | 22 | 23 | CATAPULT LMS Test Course: 205-3-duplicated-au 24 | 25 | 26 | 27 | 28 | 29 | <langstring lang="en">CATAPULT LMS Test AU: 205-3-duplicated-au</langstring> 30 | 31 | 32 | CATAPULT LMS Test AU: 205-3-duplicated-au 33 | 34 | http://example.com/index.html 35 | 36 | 37 | 38 | <langstring lang="en">CATAPULT LMS Test AU: 205-3-duplicated-au</langstring> 39 | 40 | 41 | CATAPULT LMS Test AU: 205-3-duplicated-au 42 | 43 | http://example.com/index.html 44 | 45 | 46 | -------------------------------------------------------------------------------- /lts/pkg/src/206-1-invalid-au-url.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 20 | <langstring lang="en">CATAPULT LMS Test Course: 205-3-duplicated-au</langstring> 21 | 22 | 23 | CATAPULT LMS Test Course: 205-3-duplicated-au 24 | 25 | 26 | 27 | 28 | <langstring lang="en">CATAPULT LMS Test AU: 205-3-duplicated-au</langstring> 29 | 30 | 31 | CATAPULT LMS Test AU: 205-3-duplicated-au 32 | 33 | 34 | http://example.com index.html 35 | 36 | 37 | -------------------------------------------------------------------------------- /lts/pkg/src/207-1-invalid-courseStructure.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 20 | <langstring lang="en">CATAPULT LMS Test Course: 207-1-invalid-courseStructure</langstring> 21 | 22 | 23 | CATAPULT LMS Test Course: 207-1-invalid-courseStructure 24 | 25 | 26 | 27 | 28 | http://example.com index.html 29 | 30 | <langstring lang="en">CATAPULT LMS Test AU: 207-1-invalid-courseStructure</langstring> 31 | 32 | 33 | CATAPULT LMS Test AU: 207-1-invalid-courseStructure 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /lts/pkg/src/208-1-invalid-package.md: -------------------------------------------------------------------------------- 1 | # LMS Test Package 2 | 3 | This is a markdown file which is not a supported type of package. 4 | 5 | Used to validate: 14.0.0.0-1 6 | -------------------------------------------------------------------------------- /lts/pkg/src/209-1-not-a-zip.zip: -------------------------------------------------------------------------------- 1 | # LMS Test Package 2 | 3 | This is a markdown file which is not a zip file but attempted to imported as a zip. 4 | 5 | Used to validate: 14.1.0.0-1 6 | -------------------------------------------------------------------------------- /lts/pkg/src/210-1-no-cmi5-xml/README.md: -------------------------------------------------------------------------------- 1 | # LMS Test Package 2 | 3 | This package intentionally doesn't include a cmi5.xml at the root of the archive 4 | 5 | Used to validate: 14.1.0.0-2 6 | -------------------------------------------------------------------------------- /lts/pkg/src/empty.js: -------------------------------------------------------------------------------- 1 | // Intentionally empty 2 | -------------------------------------------------------------------------------- /lts/pkg/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | <%= htmlWebpackPlugin.options.title %> 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /player/.dockerignore: -------------------------------------------------------------------------------- 1 | **/*.swp 2 | **/node_modules/ 3 | -------------------------------------------------------------------------------- /player/.env.example: -------------------------------------------------------------------------------- 1 | HOSTNAME=localhost 2 | HOST_PORT=63398 3 | 4 | DB_HOST=rdbms 5 | DB_NAME=catapult_player 6 | DB_USERNAME=catapult 7 | DB_PASSWORD=quartz 8 | 9 | CONTENT_URL=http://localhost:63398/content 10 | 11 | PLAYER_ALTERNATE_AUTH_HEADER= 12 | PLAYER_REQUIRE_STRICT_HEADERS= 13 | 14 | API_KEY="some API access key" 15 | API_SECRET="an API access secret" 16 | TOKEN_SECRET="some random string" 17 | 18 | LRS_ENDPOINT="The LRS endpoint" 19 | LRS_USERNAME="LRS username" 20 | LRS_PASSWORD="LRS password" 21 | LRS_XAPI_VERSION="version if needed 22 | 23 | ## Default is empty, add if necessary for nginx config 24 | PLAYER_API_ROOT= 25 | PLAYER_STANDALONE_LAUNCH_URL_BASE= 26 | -------------------------------------------------------------------------------- /player/Dockerfile: -------------------------------------------------------------------------------- 1 | # Copyright 2021 Rustici Software 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | FROM node:18 16 | RUN apt-get update && apt-get install dumb-init 17 | ENV NODE_ENV production 18 | WORKDIR /usr/src/app 19 | COPY --chown=node:node entrypoint.sh /usr/src/app 20 | COPY --chown=node:node service /usr/src/app 21 | COPY --chown=node:node migrations /usr/src/app/migrations 22 | 23 | RUN mkdir -p /usr/src/app/var/content && chown node:node /usr/src/app/var/content 24 | 25 | RUN npm ci --only=production 26 | RUN npm install -g nodemon 27 | USER node 28 | ENTRYPOINT [] 29 | CMD ["dumb-init", "./entrypoint.sh"] 30 | EXPOSE 3398/tcp 31 | -------------------------------------------------------------------------------- /player/arch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adlnet/CATAPULT/515126a016718d199abc008d027e89d1f9d8775f/player/arch.png -------------------------------------------------------------------------------- /player/certbot/.gitignore: -------------------------------------------------------------------------------- 1 | log/ -------------------------------------------------------------------------------- /player/docker-compose.yml: -------------------------------------------------------------------------------- 1 | # Copyright 2021 Rustici Software 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | version: "3.8" 15 | services: 16 | webservice: 17 | build: . 18 | image: catapult:player 19 | ports: 20 | - ${HOST_PORT}:3398 21 | depends_on: 22 | - rdbms 23 | volumes: 24 | - ./service/index.js:/usr/src/app/index.js:ro 25 | - ./service/knexfile.js:/usr/src/app/knexfile.js:ro 26 | - ./service/plugins:/usr/src/app/plugins:ro 27 | - ./service/lib:/usr/src/app/lib:ro 28 | - ./migrations:/usr/src/app/migrations:ro 29 | - ./seeds:/usr/src/app/seeds:ro 30 | environment: 31 | - CONTENT_URL 32 | - API_KEY 33 | - API_SECRET 34 | - TOKEN_SECRET 35 | - LRS_ENDPOINT 36 | - LRS_USERNAME 37 | - LRS_PASSWORD 38 | - LRS_XAPI_VERSIONs 39 | - DB_HOST 40 | - DB_NAME 41 | - DB_USERNAME 42 | - DB_PASSWOD 43 | # - DATABASE_USER=catapult 44 | # - DATABASE_USER_PASSWORD=quartz 45 | # - DATABASE_NAME=catapult_player 46 | - FIRST_TENANT_NAME 47 | - PLAYER_API_ROOT=${PLAYER_ROOT_PATH} 48 | - PLAYER_STANDALONE_LAUNCH_URL_BASE 49 | - PLAYER_REQUIRE_STRICT_HEADERS 50 | - HOST_PORT 51 | rdbms: 52 | image: mysql:8.0.31 53 | volumes: 54 | - catapult-player-data:/var/lib/mysql 55 | environment: 56 | - MYSQL_RANDOM_ROOT_PASSWORD=yes 57 | - MYSQL_USER=catapult 58 | - MYSQL_PASSWORD=quartz 59 | - MYSQL_DATABASE=catapult_player 60 | command: [ 61 | "mysqld", 62 | 63 | # provide for full UTF-8 support 64 | "--character-set-server=utf8mb4", 65 | "--collation-server=utf8mb4_unicode_ci", 66 | 67 | # need the following because the mysql.js client lib doesn't yet support 68 | # the newer default scheme used in MySQL 8.x 69 | "--default-authentication-plugin=mysql_native_password" 70 | ] 71 | 72 | volumes: 73 | catapult-player-data: 74 | -------------------------------------------------------------------------------- /player/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Copyright 2021 Rustici Software 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | if [ RUN_MIGRATIONS ]; then 18 | node node_modules/.bin/knex migrate:latest 19 | fi 20 | 21 | # legacy-watch is used because it improves auto restart in our specific 22 | # use case for development, mounted volume in container, see "Application 23 | # isn't restarting" in the docs 24 | exec nodemon --legacy-watch --watch index.js --watch knexfile.js --watch lib --watch plugins index.js 25 | -------------------------------------------------------------------------------- /player/migrations/010-table-tenants.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021 Rustici Software 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | const tableName = "tenants"; 17 | const defaultTenantName = process.env.FIRST_TENANT_NAME; 18 | 19 | exports.up = async (knex) => { 20 | await knex.schema.createTable( 21 | tableName, 22 | (table) => { 23 | table.increments("id"); 24 | table.timestamp("created_at").notNullable().defaultTo(knex.raw("CURRENT_TIMESTAMP")); 25 | table.timestamp("updated_at").notNullable().defaultTo(knex.raw("CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP")); 26 | table.string("code").notNullable().unique(); 27 | } 28 | ); 29 | //Check to see if there is a value in First_tenant_name 30 | if(defaultTenantName){ 31 | //On creating new database, create new tenant, will automatically be '1' 32 | await knex(tableName).insert({code: defaultTenantName}) 33 | .then( function (result) { 34 | console.log("First tenant created named " + defaultTenantName) });// respond back to request 35 | } 36 | else{ 37 | console.log("There is no specified default tenant name. No default tenant created.") 38 | } 39 | }; 40 | exports.down = (knex) => knex.schema.dropTable(tableName); 41 | -------------------------------------------------------------------------------- /player/migrations/020-table-courses.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021 Rustici Software 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | const tableName = "courses"; 17 | 18 | exports.up = (knex) => knex.schema.createTable( 19 | tableName, 20 | (table) => { 21 | table.increments("id"); 22 | table.timestamp("created_at").notNullable().defaultTo(knex.raw("CURRENT_TIMESTAMP")); 23 | table.timestamp("updated_at").notNullable().defaultTo(knex.raw("CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP")); 24 | table.integer("tenant_id").unsigned().notNullable().references("id").inTable("tenants").onUpdate("CASCADE").onDelete("RESTRICT"); 25 | 26 | table.string("lms_id").notNullable().unique(); 27 | 28 | table.json("metadata").notNullable(); 29 | table.json("structure").notNullable(); 30 | } 31 | ); 32 | exports.down = (knex) => knex.schema.dropTable(tableName); 33 | -------------------------------------------------------------------------------- /player/migrations/025-table-courses_aus.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021 Rustici Software 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | const tableName = "courses_aus"; 17 | 18 | exports.up = (knex) => knex.schema.createTable( 19 | tableName, 20 | (table) => { 21 | table.increments("id"); 22 | table.timestamp("created_at").notNullable().defaultTo(knex.raw("CURRENT_TIMESTAMP")); 23 | table.timestamp("updated_at").notNullable().defaultTo(knex.raw("CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP")); 24 | table.integer("tenant_id").unsigned().notNullable().references("id").inTable("tenants").onUpdate("CASCADE").onDelete("RESTRICT"); 25 | 26 | table.integer("course_id").unsigned().notNullable().references("id").inTable("courses").onUpdate("CASCADE").onDelete("CASCADE"); 27 | 28 | table.integer("au_index").unsigned().notNullable(); 29 | table.string("lms_id").notNullable().unique(); 30 | 31 | // 32 | // contains values from the course structure specific to the AU such as 33 | // activityType, launchMethod, launchParameters, moveOn, masteryScore, entitlementKey 34 | // 35 | table.json("metadata").notNullable(); 36 | 37 | table.unique(["course_id", "au_index"]); 38 | } 39 | ); 40 | exports.down = (knex) => knex.schema.dropTable(tableName); 41 | -------------------------------------------------------------------------------- /player/migrations/030-table-registrations.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021 Rustici Software 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | const tableName = "registrations"; 17 | 18 | exports.up = (knex) => knex.schema.createTable( 19 | tableName, 20 | (table) => { 21 | table.increments("id"); 22 | table.timestamp("created_at").notNullable().defaultTo(knex.raw("CURRENT_TIMESTAMP")); 23 | table.timestamp("updated_at").notNullable().defaultTo(knex.raw("CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP")); 24 | table.integer("tenant_id").unsigned().notNullable().references("id").inTable("tenants").onUpdate("CASCADE").onDelete("RESTRICT"); 25 | 26 | table.string("code").notNullable().unique(); 27 | table.integer("course_id").unsigned().notNullable().references("id").inTable("courses").onUpdate("CASCADE").onDelete("CASCADE"); 28 | table.json("actor").notNullable(); 29 | table.boolean("is_satisfied").notNullable().default(false); 30 | 31 | table.json("metadata").notNullable(); 32 | } 33 | ); 34 | exports.down = (knex) => knex.schema.dropTable(tableName); 35 | -------------------------------------------------------------------------------- /player/migrations/040-table-registrations_courses_aus.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021 Rustici Software 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | const tableName = "registrations_courses_aus"; 17 | 18 | exports.up = (knex) => knex.schema.createTable( 19 | tableName, 20 | (table) => { 21 | table.increments("id"); 22 | table.timestamp("created_at").notNullable().defaultTo(knex.raw("CURRENT_TIMESTAMP")); 23 | table.timestamp("updated_at").notNullable().defaultTo(knex.raw("CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP")); 24 | table.integer("tenant_id").unsigned().notNullable().references("id").inTable("tenants").onUpdate("CASCADE").onDelete("RESTRICT"); 25 | 26 | table.integer("course_au_id").unsigned().notNullable().references("id").inTable("courses_aus").onUpdate("CASCADE").onDelete("CASCADE"); 27 | table.integer("registration_id").unsigned().notNullable().references("id").inTable("registrations").onUpdate("CASCADE").onDelete("CASCADE"); 28 | 29 | table.boolean("has_been_attempted").notNullable().default(false); 30 | table.boolean("has_been_browsed").notNullable().default(false); 31 | table.boolean("has_been_reviewed").notNullable().default(false); 32 | table.integer("duration_normal").unsigned(); 33 | table.integer("duration_browse").unsigned(); 34 | table.integer("duration_review").unsigned(); 35 | table.boolean("is_passed").notNullable().default(false); 36 | table.boolean("is_completed").notNullable().default(false); 37 | table.boolean("is_waived").notNullable().default(false); 38 | table.string("waived_reason"); 39 | 40 | // is_satisfied here is used to track when an AU was already satisfied 41 | table.boolean("is_satisfied").notNullable().default(false); 42 | 43 | table.json("metadata").notNullable(); 44 | } 45 | ); 46 | exports.down = (knex) => knex.schema.dropTable(tableName); 47 | -------------------------------------------------------------------------------- /player/service/lib/db.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021 Rustici Software 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | const Knex = require("knex"), 17 | KnexStringcase = require("knex-stringcase"), 18 | KnexCfg = require("../knexfile"); 19 | 20 | module.exports = async () => { 21 | const knexCfg = await KnexCfg(); 22 | 23 | return Knex(KnexStringcase(knexCfg)); 24 | }; 25 | -------------------------------------------------------------------------------- /player/service/node_modules.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adlnet/CATAPULT/515126a016718d199abc008d027e89d1f9d8775f/player/service/node_modules.tar.gz -------------------------------------------------------------------------------- /player/service/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "catapult-player-service", 3 | "version": "1.0.0", 4 | "description": "Web service implementing a cmi5 launching system to be integrated with an LMS", 5 | "repository": { 6 | "type": "git", 7 | "url": "git+https://github.com/adlnet/CATAPULT.git" 8 | }, 9 | "scripts": { 10 | "start": "node index.js", 11 | "test": "mocha ./tests/**/*.spec.js" 12 | }, 13 | "config": { 14 | "port": "3398" 15 | }, 16 | "nodemonConfig": { 17 | "watch": [ 18 | "./index.js", 19 | "lib/", 20 | "node_modules/" 21 | ] 22 | }, 23 | "license": "ISC", 24 | "dependencies": { 25 | "@cmi5/requirements": "^1.0.0", 26 | "@hapi/basic": "^6.0.0", 27 | "@hapi/boom": "^9.1.3", 28 | "@hapi/h2o2": "^9.1.0", 29 | "@hapi/hapi": "^20.1.5", 30 | "@hapi/hoek": "^9.2.0", 31 | "@hapi/inert": "^6.0.3", 32 | "@hapi/jwt": "^2.0.1", 33 | "@hapi/vision": "^6.1.0", 34 | "@hapi/wreck": "^17.1.0", 35 | "axios": "^1.7.1", 36 | "hapi-swagger": "15.0.0", 37 | "iri": "^1.3.0", 38 | "iso8601-duration": "^1.3.0", 39 | "joi": "^17.4.1", 40 | "knex": "^0.95.7", 41 | "knex-stringcase": "^1.4.5", 42 | "libxmljs": "^0.19.8", 43 | "mysql": "^2.18.1", 44 | "node-stream-zip": "^1.13.6", 45 | "uuid": "^9.0.1", 46 | "wait-port": "^0.2.9", 47 | "xml-sanitizer": "^2.0.1" 48 | }, 49 | "devDependencies": { 50 | "chai": "^4", 51 | "dotenv": "^16.4.5", 52 | "mocha": "^10.4.0" 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /player/service/plugins/routes/content.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021 Rustici Software 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | "use strict"; 17 | 18 | module.exports = { 19 | name: "catapult-player-api-routes-content", 20 | register: (server, options) => { 21 | server.route( 22 | { 23 | method: "GET", 24 | path: "/content/{param*}", 25 | handler: { 26 | directory: { 27 | path: `${__dirname}/../../var/content` 28 | } 29 | }, 30 | options: { 31 | auth: false 32 | } 33 | } 34 | ); 35 | } 36 | }; 37 | -------------------------------------------------------------------------------- /player/service/plugins/routes/v1/sessions.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021 Rustici Software 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | "use strict"; 17 | 18 | const Boom = require("@hapi/boom"), 19 | Wreck = require("@hapi/wreck"), 20 | Session = require("../lib/session"); 21 | 22 | module.exports = { 23 | name: "catapult-player-api-routes-v1-sessions", 24 | register: (server, options) => { 25 | server.route( 26 | [ 27 | { 28 | method: "GET", 29 | path: "/session/{id}", 30 | options: { 31 | tags: ["api"] 32 | }, 33 | handler: async (req, h) => { 34 | const result = await Session.load(req.params.id, req.auth.credentials.tenantId, {db: req.server.app.db}); 35 | 36 | if (! result) { 37 | return Boom.notFound(); 38 | } 39 | 40 | return result; 41 | } 42 | }, 43 | 44 | { 45 | method: "POST", 46 | path: "/session/{id}/abandon", 47 | options: { 48 | tags: ["api"] 49 | }, 50 | handler: async (req, h) => { 51 | const sessionId = req.params.id, 52 | tenantId = req.auth.credentials.tenantId, 53 | db = req.server.app.db, 54 | lrsWreck = Wreck.defaults(await req.server.methods.lrsWreckDefaults(req)); 55 | 56 | await Session.abandon(sessionId, tenantId, "api", {db, lrsWreck}); 57 | return null; 58 | } 59 | } 60 | ] 61 | ); 62 | } 63 | }; 64 | -------------------------------------------------------------------------------- /player/service/tests/files/entity.xml: -------------------------------------------------------------------------------- 1 | 2 | ]> 4 | &xxe; 5 | -------------------------------------------------------------------------------- /player/service/tests/lrs.spec.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | require("dotenv").config({ 3 | path: path.join(__dirname, "../../.env") 4 | }); 5 | 6 | const axios = require("axios").default; 7 | const mocha = require("mocha"); 8 | const chai = require("chai"); 9 | const uuid = require("uuid"); 10 | 11 | const helpers = require("../plugins/routes/lib/helpers"); 12 | 13 | describe("Basic LRS Communications", async () => { 14 | 15 | 16 | it ("Sends a statement via basic PUT requests to the ADL LRS", async() => { 17 | 18 | let statement = { 19 | "actor": { 20 | "name": "Sally Glider", 21 | "mbox": "mailto:sally@example.com" 22 | }, 23 | "verb": { 24 | "id": "http://adlnet.gov/expapi/verbs/experienced", 25 | "display": { "en-US": "experienced" } 26 | }, 27 | "object": { 28 | "id": "http://example.com/activities/solo-hang-gliding", 29 | "definition": { 30 | "name": { "en-US": "Solo Hang Gliding" } 31 | } 32 | }, 33 | id: uuid.v4() 34 | }; 35 | 36 | let path = "/statements?statementId=" + statement.id; 37 | 38 | console.log("Sending statement ..."); 39 | 40 | let res = await helpers.sendDocumentToLRS(path, "PUT", statement); 41 | 42 | chai.expect(res.status).to.be.lessThan(400); 43 | chai.expect(res.status).to.be.greaterThanOrEqual(200); 44 | }); 45 | }); 46 | -------------------------------------------------------------------------------- /player/service/tests/xml.spec.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | require("dotenv").config({ 3 | path: path.join(__dirname, "../../.env") 4 | }); 5 | 6 | const fs = require("fs"); 7 | 8 | const helpers = require("../plugins/routes/lib/helpers"); 9 | const chai = require("chai"); 10 | const mocha = require("mocha"); 11 | const exp = require("constants"); 12 | 13 | describe("XML Parsing and Usage", async () => { 14 | 15 | /** 16 | * https://www.stackhawk.com/blog/nodejs-xml-external-entities-xxe-guide-examples-and-prevention/ 17 | */ 18 | const PATH_XML_DOC_ENTITY_USAGE = path.join(__dirname, "files/entity.xml"); 19 | 20 | it ("Rejects anything using an ENTITY tag", async() => { 21 | 22 | let entityBody = fs.readFileSync(PATH_XML_DOC_ENTITY_USAGE); 23 | let suspicious = await helpers.isPotentiallyMaliciousXML(entityBody); 24 | 25 | chai.expect(suspicious).to.be.equal(true, "The provided XML should have thrown a validity issue for its use of an { 29 | 30 | let providedText = '\u0000Some text\u0000🎉🎉\u0000'; 31 | let expectedText = 'Some text'; 32 | 33 | let parsedText = await helpers.sanitizeXML(providedText); 34 | 35 | chai.expect(parsedText).to.be.equal(expectedText, "The provided XML was not parsed into the expected text"); 36 | }); 37 | 38 | 39 | it ("Handles when a Buffer is provided instead of a string", async() => { 40 | 41 | let providedText = '\u0000Some text\u0000🎉🎉\u0000'; 42 | let providedBuffer = Buffer.from(providedText); 43 | let expectedText = 'Some text'; 44 | 45 | let parsedText = await helpers.sanitizeXML(providedBuffer); 46 | 47 | chai.expect(parsedText).to.be.equal(expectedText, "The provided XML was not parsed into the expected text"); 48 | }); 49 | }); 50 | -------------------------------------------------------------------------------- /requirements/NOTICE: -------------------------------------------------------------------------------- 1 | Catapult: cmi5 requirements 2 | Copyright 2021 Rustici Software 3 | 4 | This product includes software developed at 5 | Rustici Software (http://www.rusticisoftware.com/). 6 | -------------------------------------------------------------------------------- /requirements/README.md: -------------------------------------------------------------------------------- 1 | # @cmi5/requirements 2 | 3 | This package is intended to track the requirements of the [cmi5 specification](https://github.com/AICC/CMI-5_Spec_Current) in a JSON file. There are two types of requirements in the file--explicit and derived. Explicit requirements are numbered based on their section in the specification followed by an ordered index of where the requirement is positioned in the section. (Note: due to potential changes in the specification over time the index order may not be true to the specification beyond the initial starting point.) Derived requirements are numbered based on the explicit requirement from which they are derived followed by a suffix of ` (d#)`. 4 | 5 | ## Install 6 | 7 | npm install @cmi5/requirements 8 | 9 | ## Usage 10 | 11 | The `requirements.json` JSON file provides a structure with an object at the top level. That object contains a property for each requirement (and derived requirements) that have as their value an object with a single `txt` property that is the wording from the specification itself. 12 | -------------------------------------------------------------------------------- /requirements/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@cmi5/requirements", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1 5 | } 6 | -------------------------------------------------------------------------------- /requirements/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@cmi5/requirements", 3 | "version": "1.0.0", 4 | "description": "JSON list of cmi5 specification requirements.", 5 | "main": "requirements.json", 6 | "repository": { 7 | "type": "git", 8 | "url": "git+https://github.com/adlnet/CATAPULT.git" 9 | }, 10 | "keywords": [ 11 | "cmi5", 12 | "xapi" 13 | ], 14 | "author": "Brian J. Miller ", 15 | "license": "Apache-2.0", 16 | "bugs": { 17 | "url": "https://github.com/adlnet/CATAPULT/issues" 18 | }, 19 | "homepage": "https://github.com/adlnet/CATAPULT#readme" 20 | } 21 | --------------------------------------------------------------------------------