first commit

This commit is contained in:
CHIEFSOFT\ameye
2024-09-30 18:11:26 -04:00
commit e592ca6823
27270 changed files with 5002257 additions and 0 deletions
+144
View File
@@ -0,0 +1,144 @@
@mod @mod_lti
Feature: Add tools
In order to provide activities for learners
As a teacher
I need to be able to add instances of external tools to a course
Background:
Given the following "users" exist:
| username | firstname | lastname | email |
| teacher1 | Terry1 | Teacher1 | teacher1@example.com |
And the following "courses" exist:
| fullname | shortname | category |
| Course 1 | C1 | 0 |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
# A site tool configured to show as a preconfigured tool and in the activity chooser.
And the following "mod_lti > tool types" exist:
| name | baseurl | coursevisible | state |
| Teaching Tool 1 | /mod/lti/tests/fixtures/tool_provider.php | 2 | 1 |
# A course tool in course 1.
And the following "mod_lti > course tools" exist:
| name | baseurl | course |
| Course tool 1 | /mod/lti/tests/fixtures/tool_provider.php | C1 |
@javascript
Scenario: Add a site tool via the activity picker
Given I log in as "teacher1"
And I am on "Course 1" course homepage with editing mode on
When I add a "Teaching Tool 1" to section "1" using the activity chooser
And I set the field "Activity name" to "Test tool activity 1"
And "Launch container" "field" should not be visible
# For tool that does not support Content-Item message type, the Select content button must be disabled.
And "Select content" "button" should not be visible
And "Tool URL" "field" should not be visible
And I press "Save and return to course"
And I am on the "Test tool activity 1" "lti activity editing" page
Then the field "Activity name" matches value "Test tool activity 1"
And "Launch container" "field" should not be visible
And "Select content" "button" should not be visible
And "Tool URL" "field" should not be visible
@javascript
Scenario: Add a course tool via the activity picker
Given I log in as "teacher1"
And I am on "Course 1" course homepage with editing mode on
When I add a "Course tool 1" to section "1" using the activity chooser
And I set the field "Activity name" to "Test tool activity 2"
And "Launch container" "field" should not be visible
# For tool that does not support Content-Item message type, the Select content button must be disabled.
And "Select content" "button" should not be visible
And I press "Save and return to course"
And I am on the "Test tool activity 2" "lti activity editing" page
Then the field "Activity name" matches value "Test tool activity 2"
And "Launch container" "field" should not be visible
And "Select content" "button" should not be visible
And "Tool URL" "field" should not be visible
@javascript
Scenario: Editing a (deprecated) manually configured activity instance, confirming that config changes aren't possible
Given the following "activities" exist:
| activity | name | course | toolurl |
| lti | A manual tool | C1 | /mod/lti/tests/fixtures/ims_cartridge_basic_lti_link.xml |
# Add a course tool with the same URL as that of the manually configured instance (the tool URL found in the above cartridge).
# This would normally be domain-matched during edit, resulting in the assignment of a preconfigured tool to the instance.
# In this case, because config changes and domain matching are disabled, the test confirms this doesn't take place.
And the following "mod_lti > course tools" exist:
| name | baseurl | course | lti_sendname | lti_sendemailaddr | lti_acceptgrades |
| Course tool 2 | http://www.example.com/lti/provider.php | C1 | 0 | 1 | 2 |
When I am on the "A manual tool" "lti activity editing" page logged in as teacher1
Then I should see "Manually configured External tool activities are no longer supported"
And I follow "Show more..."
And I expand all fieldsets
# The privacy values below represent the existing values of the privacy settings, before saving and inheriting from the
# domain-matched tool values.
And the following fields match these values:
| Activity name | A manual tool |
| id_showdescription | 0 |
| Consumer key | 12345 |
| Icon URL | http://download.moodle.org/unittest/test.jpg |
| Secure icon URL | https://download.moodle.org/unittest/test.jpg |
| Tool URL | http://www.example.com/lti/provider.php |
| id_instructorchoicesendname | 1 |
| id_instructorchoicesendemailaddr | 1 |
| id_instructorchoiceacceptgrades | 1 |
And the "Activity name" "field" should be enabled
And the "Activity description" "field" should be enabled
And the "id_showdescription" "checkbox" should be enabled
And the "id_showtitlelaunch" "checkbox" should be enabled
And the "id_showdescriptionlaunch" "checkbox" should be enabled
And the "Secure tool URL" "field" should be disabled
And the "Consumer key" "field" should be enabled
And the "Shared secret" "field" should be enabled
And I click on "Reveal" "icon"
And I should see "secret"
And the "Custom parameters" "field" should be disabled
And the "Icon URL" "field" should be disabled
And the "Secure icon URL" "field" should be disabled
And I should see "Automatic, based on tool URL"
And the "Select content" "button" should be disabled
And the "Tool URL" "field" should be disabled
And the "id_instructorchoicesendname" "checkbox" should be disabled
And the "id_instructorchoicesendemailaddr" "checkbox" should be disabled
And the "id_instructorchoiceacceptgrades" "checkbox" should be disabled
And I set the following fields to these values:
| Activity name | A manual tool name edited |
| id_showdescription | 1 |
| Consumer key | key |
| Shared secret | secret |
And I press "Save and return to course"
And I am on the "A manual tool" "lti activity editing" page logged in as teacher1
And I follow "Show more..."
# This confirms that the instance config, while locked to user edits, still inherits privacy settings from the tool which
# it was domain-matched to.
And the following fields match these values:
| Activity name | A manual tool name edited |
| id_showdescription | 1 |
| Consumer key | key |
| Shared secret | secret |
| Icon URL | http://download.moodle.org/unittest/test.jpg |
| Secure icon URL | https://download.moodle.org/unittest/test.jpg |
| Tool URL | http://www.example.com/lti/provider.php |
| id_instructorchoicesendname | 0 |
| id_instructorchoicesendemailaddr | 1 |
| id_instructorchoiceacceptgrades | 2 |
And the "Activity name" "field" should be enabled
And the "Activity description" "field" should be enabled
And the "id_showdescription" "checkbox" should be enabled
And the "id_showtitlelaunch" "checkbox" should be enabled
And the "id_showdescriptionlaunch" "checkbox" should be enabled
And the "Secure tool URL" "field" should be disabled
And the "Consumer key" "field" should be enabled
And the "Shared secret" "field" should be enabled
And I click on "Reveal" "icon"
And I should see "secret"
And the "Custom parameters" "field" should be disabled
And the "Icon URL" "field" should be disabled
And the "Secure icon URL" "field" should be disabled
And I should see "Automatic, based on tool URL"
And the "Select content" "button" should be disabled
And the "Tool URL" "field" should be disabled
And the "id_instructorchoicesendname" "checkbox" should be disabled
And the "id_instructorchoicesendemailaddr" "checkbox" should be disabled
And the "id_instructorchoiceacceptgrades" "checkbox" should be disabled
@@ -0,0 +1,64 @@
@mod @mod_lti @core_backup @javascript
Feature: Restoring Moodle 2 backup restores LTI configuration
Background:
Given the following "users" exist:
| username | firstname | lastname | email |
| teacher1 | Terry1 | Teacher1 | teacher1@example.com |
And the following "courses" exist:
| fullname | shortname | category |
| Course 1 | C1 | 0 |
| Course 2 | C2 | 0 |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
| teacher1 | C2 | editingteacher |
And the following config values are set as admin:
| enableasyncbackup | 0 |
Scenario: Backup and restore course with preconfigured site LTI tool on the same site
Given the following "mod_lti > tool types" exist:
| name | description | baseurl | coursevisible | state |
| My site tool | Site tool description | https://www.moodle.org | 2 | 1 |
And the following "mod_lti > tool instances" exist:
| name | tool | course |
| My LTI module | My site tool | C1 |
And I am on the "Course 1" course page logged in as admin
And I should see "My LTI module"
When I backup "Course 1" course using this options:
| Confirmation | Filename | test_backup.mbz |
And I restore "test_backup.mbz" backup into a new course using this options:
And I am on site homepage
And I follow "Course 1 copy 1"
Then I should see "My LTI module"
And I navigate to "Plugins > Activity modules > External tool > Manage tools" in site administration
And "This tool is being used 2 times" "text" should exist in the "//div[contains(@id,'tool-card-container') and contains(., 'My site tool')]" "xpath_element"
@javascript
Scenario: Backup and restore course with preconfigured course LTI tool on the same site
Given the following "mod_lti > course tools" exist:
| name | description | baseurl | course | lti_resourcekey | lti_password | lti_launchcontainer |
| My course tool | Example description | http://www.example.com/lti/provider.php | C1 | my key | my secret | 5 |
# In the first course create an LTI module that uses a course preconfigured tool
And the following "mod_lti > tool instances" exist:
| name | tool | course |
| Test tool activity 2 | My course tool | C1 |
And I am on the "Course 1" course page logged in as admin
# Backup course and restore into another course
And I backup "Course 1" course using this options:
| Confirmation | Filename | test_backup.mbz |
When I restore "test_backup.mbz" backup into "Course 2" course using this options:
# Make sure the copy of the preconfigured tool was created in the second course with both encrypted and non-encrypted properties.
And I am on "Course 2" course homepage with editing mode on
And I open "Test tool activity 2" actions menu
And I choose "Edit settings" in the open action menu
And the field "Activity name" matches value "Test tool activity 2"
And I am on "Course 1" course homepage
And I navigate to "LTI External tools" in current page administration
Then I should see "My course tool"
And I open the action menu in "My course tool" "table_row"
And I choose "Edit" in the open action menu
And the field "Tool URL" matches value "http://www.example.com/lti/provider.php"
And the field "Consumer key" matches value "my key"
And the field "Shared secret" matches value "my secret"
And the field "Default launch container" matches value "Existing window"
+35
View File
@@ -0,0 +1,35 @@
@mod @mod_lti
Feature: Content-Item support
In order to easily add activities and content in a course from an external tool
As a teacher
I need to utilise a tool that supports the Deep Linking (Content-Item Message) type
Background:
Given the following "users" exist:
| username | firstname | lastname | email |
| teacher1 | Terry1 | Teacher1 | teacher1@example.com |
And the following "courses" exist:
| fullname | shortname | category |
| Course 1 | C1 | 0 |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
And the following "mod_lti > tool types" exist:
| name | description | baseurl | coursevisible | state | lti_contentitem |
| Teaching Tool 1 | Tool 1 description | /mod/lti/tests/fixtures/tool_provider.php | 2 | 1 | 1 |
@javascript
Scenario: Tool that supports Deep Linking should be able to configure a tool via the Select content button
Given I log in as "teacher1"
And I am on "Course 1" course homepage with editing mode on
When I add a "Teaching Tool 1" to section "1" using the activity chooser
Then "Select content" "button" should be visible
And the "Select content" "button" should be enabled
Scenario: Editing the settings for an instance of a tool configured with Deep Linking support
Given the following "mod_lti > tool instances" exist:
| name | tool | course |
| Test tool activity 1 | Teaching Tool 1 | C1 |
When I am on the "Test tool activity 1" "lti activity editing" page logged in as teacher1
Then I should see "Select content"
And the "Select content" "button" should be enabled
@@ -0,0 +1,32 @@
@mod @mod_lti
Feature: Create/edit tool configuration that has Deep Linking support
In order to provide external tools that support Deep Linking for teachers and learners
As an admin
I need to be able to configure external tool registrations that support Deep Linking.
Background:
Given I log in as "admin"
And I navigate to "Plugins > Activity modules > External tool > Manage tools" in site administration
Scenario: Verifying ContentItemSelectionRequest selection support in external tool registration
When I follow "Manage external tool registrations"
And I follow "Configure a new external tool registration"
Then I should see "ContentItemSelectionRequest" in the "Capabilities" "select"
@javascript
Scenario: Creating and editing tool configuration that has Content-Item support
When I follow "configure a tool manually"
And I set the field "Tool name" to "Test tool"
And I set the field "Tool URL" to local url "/mod/lti/tests/fixtures/tool_provider.php"
And I set the field "Tool configuration usage" to "Show in activity chooser and as a preconfigured tool"
And I expand all fieldsets
And I set the field "Supports Deep Linking (Content-Item Message)" to "1"
And I press "Save changes"
And I follow "Edit"
And I expand all fieldsets
Then the field "Supports Deep Linking (Content-Item Message)" matches value "1"
And I set the field "Supports Deep Linking (Content-Item Message)" to "0"
And I press "Save changes"
And I follow "Edit"
And I expand all fieldsets
And the field "Supports Deep Linking (Content-Item Message)" matches value "0"
@@ -0,0 +1,53 @@
@mod @mod_lti @core_completion
Feature: View activity completion information in the LTI activity
In order to have visibility of LTI completion requirements
As a student
I need to be able to view my LTI completion progress
Background:
Given the following "users" exist:
| username | firstname | lastname | email |
| student1 | Vinnie | Student1 | student1@example.com |
| teacher1 | Darrell | Teacher1 | teacher1@example.com |
And the following "courses" exist:
| fullname | shortname | category | enablecompletion | showcompletionconditions |
| Course 1 | C1 | 0 | 1 | 1 |
And the following "course enrolments" exist:
| user | course | role |
| student1 | C1 | student |
| teacher1 | C1 | editingteacher |
And the following "activities" exist:
| activity | name | course | idnumber | completion | completionview | completionusegrade |
| lti | Music history | C1 | lti1 | 2 | 1 | 1 |
Scenario: View automatic completion items as a teacher
Given I am on the "Music history" "lti activity" page logged in as teacher1
Then "Music history" should have the "Receive a grade" completion condition
And "Music history" should have the "View" completion condition
@javascript
Scenario: View automatic completion items as a student
Given I am on the "Music history" "lti activity" page logged in as student1
And the "View" completion condition of "Music history" is displayed as "done"
And the "Receive a grade" completion condition of "Music history" is displayed as "todo"
And I am on the "Course 1" "grades > Grader report > View" page logged in as "teacher1"
And I turn editing mode on
And I give the grade "90.00" to the user "Vinnie Student1" for the grade item "Music history"
And I press "Save changes"
When I am on the "Music history" "lti activity" page logged in as student1
Then the "Receive a grade" completion condition of "Music history" is displayed as "done"
And the "View" completion condition of "Music history" is displayed as "done"
@javascript
Scenario: Use manual completion
Given I am on the "Music history" "lti activity editing" page logged in as teacher1
And I expand all fieldsets
And I set the field "Students must manually mark the activity as done" to "1"
And I press "Save and display"
# Teacher view.
And the manual completion button for "Music history" should be disabled
# Student view.
When I am on the "Music history" "lti activity" page logged in as student1
Then the manual completion button of "Music history" is displayed as "Mark as done"
And I toggle the manual completion state of "Music history"
And the manual completion button of "Music history" is displayed as "Done"
@@ -0,0 +1,56 @@
@mod @mod_lti @core_completion
Feature: Pass grade activity completion information in the LTI activity
Background:
Given the following "users" exist:
| username | firstname | lastname | email |
| student1 | Vinnie | Student1 | student1@example.com |
| student2 | Vinnie | Student2 | student2@example.com |
| student3 | Vinnie | Student3 | student3@example.com |
| teacher1 | Darrell | Teacher1 | teacher1@example.com |
And the following "courses" exist:
| fullname | shortname | category | enablecompletion | showcompletionconditions |
| Course 1 | C1 | 0 | 1 | 1 |
And the following "course enrolments" exist:
| user | course | role |
| student1 | C1 | student |
| student2 | C1 | student |
| student3 | C1 | student |
| teacher1 | C1 | editingteacher |
And the following "activities" exist:
| activity | name | course | gradepass | completion | completionview | completionusegrade | completionpassgrade |
| lti | Music history | C1 | 50 | 2 | 1 | 1 | 1 |
Scenario: View automatic completion items as a teacher
Given I am on the "Music history" "lti activity" page logged in as teacher1
Then "Music history" should have the "View" completion condition
And "Music history" should have the "Receive a grade" completion condition
And "Music history" should have the "Receive a passing grade" completion condition
@javascript
Scenario: View automatic completion items as a student
Given I am on the "Music history" "lti activity" page logged in as student1
And the "View" completion condition of "Music history" is displayed as "done"
And the "Receive a grade" completion condition of "Music history" is displayed as "todo"
And the "Receive a passing grade" completion condition of "Music history" is displayed as "todo"
And I am on the "Course 1" "grades > Grader report > View" page logged in as "teacher1"
And I turn editing mode on
And I give the grade "90.00" to the user "Vinnie Student1" for the grade item "Music history"
And I give the grade "20.00" to the user "Vinnie Student2" for the grade item "Music history"
And I press "Save changes"
When I am on the "Music history" "lti activity" page logged in as student1
Then the "Receive a grade" completion condition of "Music history" is displayed as "done"
Then the "Receive a passing grade" completion condition of "Music history" is displayed as "done"
And the "View" completion condition of "Music history" is displayed as "done"
When I am on the "Music history" "lti activity" page logged in as student2
Then the "Receive a grade" completion condition of "Music history" is displayed as "done"
Then the "Receive a passing grade" completion condition of "Music history" is displayed as "failed"
And the "View" completion condition of "Music history" is displayed as "done"
When I am on the "Music history" "lti activity" page logged in as student3
Then the "Receive a grade" completion condition of "Music history" is displayed as "todo"
Then the "Receive a passing grade" completion condition of "Music history" is displayed as "todo"
And the "View" completion condition of "Music history" is displayed as "done"
And I am on the "Course 1" course page logged in as teacher1
And "Vinnie Student1" user has completed "Music history" activity
And "Vinnie Student2" user has completed "Music history" activity
And "Vinnie Student3" user has not completed "Music history" activity
@@ -0,0 +1,290 @@
@mod @mod_lti
Feature: Manage course tools
In order to provide richer experiences for learners
As a teacher
I need to be able to add external tools to a course
Background:
Given the following "users" exist:
| username | firstname | lastname | email |
| teacher1 | Terry1 | Teacher1 | teacher1@example.com |
And the following "course" exists:
| fullname | Course 1 |
| shortname | C1 |
| category | 0 |
| format | topics |
| numsections | 1 |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
And the following config values are set as admin:
| enableasyncbackup | 0 |
Scenario: Create a course tool from the zero state
Given I am on the "Course 1" course page logged in as teacher1
And I navigate to "LTI External tools" in current page administration
And I should see "LTI External tools are add-on apps"
And I should see "There are no LTI External tools yet."
When I click on "Add tool" "link"
And I press "Cancel"
Then I should see "LTI External tools are add-on apps"
And I should see "There are no LTI External tools yet."
And I click on "Add tool" "link"
And I set the following fields to these values:
| Tool name | Teaching Tool 1 |
| Tool URL | http://example.com |
| Tool description | A short description of the tool |
And I press "Save changes"
And I should see "Teaching Tool 1 added"
And I should see "A short description of the tool" in the "Teaching Tool 1" "table_row"
Scenario: Viewing a site level tool in the course tools table
# The first tool isn't visible in courses, the next two are, and the last tool is in a pending state and is not visible.
Given the following "mod_lti > tool types" exist:
| name | description | baseurl | coursevisible | state |
| Example tool | Another description | https://example.com/tool1 | 0 | 1 |
| Test tool 2 | Tool2 description | https://example.com/tool2 | 1 | 1 |
| Test tool 3 | Tool3 description | https://example.com/tool3 | 2 | 1 |
| Test tool 4 | Tool4 description | https://example.com/tool4 | 2 | 2 |
And I am on the "Course 1" course page logged in as teacher1
When I navigate to "LTI External tools" in current page administration
Then I should see "Test tool 2" in the "reportbuilder-table" "table"
And "You don't have permission to edit this tool" "icon" should exist in the "Test tool 2" "table_row"
And I should see "Test tool 3" in the "reportbuilder-table" "table"
And "You don't have permission to edit this tool" "icon" should exist in the "Test tool 3" "table_row"
And I should not see "Example tool" in the "reportbuilder-table" "table"
And I should not see "Test tool 4" in the "reportbuilder-table" "table"
Scenario: Viewing course tools without the capability to add/edit but having the capability to use
Given the following "role capability" exists:
| role | editingteacher |
| mod/lti:addcoursetool | prohibit |
| mod/lti:addpreconfiguredinstance | allow |
And the following "mod_lti > course tools" exist:
| name | description | baseurl | course |
| Test tool | Example description | https://example.com/tool | C1 |
And I am on the "Course 1" course page logged in as teacher1
When I navigate to "LTI External tools" in current page administration
Then "You don't have permission to edit this tool" "icon" should exist in the "Test tool" "table_row"
Scenario: Viewing course tools with the capability to add/edit and without the capability to use
Given the following "role capability" exists:
| role | editingteacher |
| mod/lti:addcoursetool | allow |
| mod/lti:addpreconfiguredinstance | prohibit |
And the following "mod_lti > course tools" exist:
| name | description | baseurl | course |
| Test tool | Example description | https://example.com/tool | C1 |
When I am on the "Course 1" course page logged in as teacher1
Then "LTI External tools" "link" should not exist in current page administration
@javascript
Scenario: Edit a course tool
Given the following "mod_lti > course tools" exist:
| name | description | baseurl | course |
| Test tool | Example description | https://example.com/tool | C1 |
And I am on the "Course 1" course page logged in as teacher1
And I navigate to "LTI External tools" in current page administration
And the "Edit" item should exist in the "Actions" action menu of the "Test tool" "table_row"
And the "Delete" item should exist in the "Actions" action menu of the "Test tool" "table_row"
When I open the action menu in "Test tool" "table_row"
And I choose "Edit" in the open action menu
And I press "Cancel"
Then I should see "Test tool" in the "reportbuilder-table" "table"
And I open the action menu in "Test tool" "table_row"
And I choose "Edit" in the open action menu
And I set the following fields to these values:
| Tool name | Test tool (edited) |
| Tool URL | http://example.com |
| Tool description | A short description of the tool (edited) |
And I press "Save changes"
And I should see "Changes saved"
And I should see "A short description of the tool (edited)" in the "Test tool (edited)" "table_row"
@javascript
Scenario: Navigate through the listing of course tools
Given 20 "mod_lti > course tools" exist with the following data:
| name | Test tool [count] |
| description | Example description [count] |
| baseurl | https://www.example.com/tool[count] |
| course | C1 |
And I am on the "Course 1" course page logged in as teacher1
When I navigate to "LTI External tools" in current page administration
Then I should see "Test tool 1" in the "reportbuilder-table" "table"
And I click on "Name" "link"
And I should see "Test tool 20" in the "reportbuilder-table" "table"
And I click on "2" "link" in the "page" "region"
And I should see "Test tool 1" in the "reportbuilder-table" "table"
@javascript
Scenario: Delete a course tool
Given the following "mod_lti > course tools" exist:
| name | description | baseurl | course |
| Test tool | Example description | https://example.com/tool | C1 |
| Another tool | Example 123 | https://another.example.com/tool | C1 |
And I am on the "Course 1" course page logged in as teacher1
And I navigate to "LTI External tools" in current page administration
When I open the action menu in "Test tool" "table_row"
And I choose "Delete" in the open action menu
Then I should see "This will delete Test tool from the available LTI tools in your course."
And I click on "Cancel" "button" in the "Delete Test tool" "dialogue"
And I should see "Test tool" in the "reportbuilder-table" "table"
And I open the action menu in "Test tool" "table_row"
And I choose "Delete" in the open action menu
And I should see "This will delete Test tool from the available LTI tools in your course."
And I click on "Delete" "button" in the "Delete Test tool" "dialogue"
And I should see "Test tool deleted"
And I should not see "Test tool" in the "reportbuilder-table" "table"
@javascript
Scenario: Add a course tool using a cartridge URL
Given I am on the "Course 1" course page logged in as teacher1
And I navigate to "LTI External tools" in current page administration
When I click on "Add tool" "link"
And I set the following fields to these values:
| Tool name | Test tool 1 |
| Tool description | Test tool 1 description |
And I set the field "Tool URL" to local url "/mod/lti/tests/fixtures/ims_cartridge_basic_lti_link.xml"
And I press "Save changes"
Then I should see "Test tool 1" in the "reportbuilder-table" "table"
# The cartridge description, if set, overrides the description set in the type edit form (bug?).
And I should see "Example tool description" in the "Test tool 1" "table_row"
And I open the action menu in "Test tool 1" "table_row"
And I choose "Edit" in the open action menu
And the field "Tool name" matches value "Test tool 1"
And the field "Tool URL" matches value "http://www.example.com/lti/provider.php"
And the field "Icon URL" matches value "http://download.moodle.org/unittest/test.jpg"
And the field "Secure icon URL" matches value "https://download.moodle.org/unittest/test.jpg"
@javascript
Scenario: Site tool appearing in activity chooser according to settings
Given the following "mod_lti > tool types" exist:
| name | baseurl | coursevisible | state |
| Teaching Tool 1 | /mod/lti/tests/fixtures/tool_provider.php | 2 | 1 |
| Teaching Tool 2 | /mod/lti/tests/fixtures/tool_provider.php | 1 | 1 |
| Teaching Tool 3 | /mod/lti/tests/fixtures/tool_provider.php | 0 | 1 |
And the following "courses" exist:
| fullname | shortname | category |
| Course 2 | C2 | 0 |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C2 | editingteacher |
And I log in as "teacher1"
And I am on "Course 1" course homepage with editing mode on
And I click on "Add an activity or resource" "button" in the "New section" "section"
And I should see "Teaching Tool 1" in the ".modal-body" "css_element"
And I should not see "Teaching Tool 2" in the ".modal-body" "css_element"
And I should not see "Teaching Tool 3" in the ".modal-body" "css_element"
And I click on "Close" "button" in the ".modal-dialog" "css_element"
And I navigate to "LTI External tools" in current page administration
And I should not see "Teaching Tool 3"
And I click on "Don't show in activity chooser" "field" in the "Teaching Tool 1" "table_row"
And I click on "Show in activity chooser" "field" in the "Teaching Tool 2" "table_row"
And I am on "Course 1" course homepage
And I click on "Add an activity or resource" "button" in the "New section" "section"
And I should not see "Teaching Tool 1" in the ".modal-body" "css_element"
And I should see "Teaching Tool 2" in the ".modal-body" "css_element"
And I should not see "Teaching Tool 3" in the ".modal-body" "css_element"
And I click on "Close" "button" in the ".modal-dialog" "css_element"
# Should not affect other courses.
And I am on "Course 2" course homepage
And I click on "Add an activity or resource" "button" in the "New section" "section"
And I should see "Teaching Tool 1" in the ".modal-body" "css_element"
And I should not see "Teaching Tool 2" in the ".modal-body" "css_element"
And I should not see "Teaching Tool 3" in the ".modal-body" "css_element"
And I click on "Close" "button" in the ".modal-dialog" "css_element"
And I am on "Course 1" course homepage
And I navigate to "LTI External tools" in current page administration
And I click on "Show in activity chooser" "field" in the "Teaching Tool 1" "table_row"
And I click on "Don't show in activity chooser" "field" in the "Teaching Tool 2" "table_row"
And I am on "Course 1" course homepage
And I click on "Add an activity or resource" "button" in the "New section" "section"
And I should see "Teaching Tool 1" in the ".modal-body" "css_element"
And I should not see "Teaching Tool 2" in the ".modal-body" "css_element"
And I should not see "Teaching Tool 3" in the ".modal-body" "css_element"
When the following "role capability" exists:
| role | editingteacher |
| mod/lti:addcoursetool | prohibit |
And I am on "Course 1" course homepage with editing mode on
And I navigate to "LTI External tools" in current page administration
Then the "Don't show in activity chooser" "field" should be disabled
And the "Show in activity chooser" "field" should be disabled
@javascript
Scenario: Course tool appearing in activity chooser according to settings
Given the following "mod_lti > course tools" exist:
| name | baseurl | course | coursevisible |
| Course Tool 1 | /mod/lti/tests/fixtures/tool_provider.php | C1 | 2 |
| Course Tool 2 | /mod/lti/tests/fixtures/tool_provider.php | C1 | 1 |
And I log in as "teacher1"
And I am on "Course 1" course homepage with editing mode on
And I click on "Add an activity or resource" "button" in the "New section" "section"
And I should see "Course Tool 1" in the ".modal-body" "css_element"
And I should not see "Course Tool 2" in the ".modal-body" "css_element"
And I click on "Close" "button" in the ".modal-dialog" "css_element"
And I navigate to "LTI External tools" in current page administration
And I click on "Don't show in activity chooser" "field" in the "Course Tool 1" "table_row"
And I click on "Show in activity chooser" "field" in the "Course Tool 2" "table_row"
And I am on "Course 1" course homepage
And I click on "Add an activity or resource" "button" in the "New section" "section"
And I should not see "Course Tool 1" in the ".modal-body" "css_element"
And I should see "Course Tool 2" in the ".modal-body" "css_element"
And I click on "Close" "button" in the ".modal-dialog" "css_element"
And I navigate to "LTI External tools" in current page administration
And I click on "Show in activity chooser" "field" in the "Course Tool 1" "table_row"
And I click on "Don't show in activity chooser" "field" in the "Course Tool 2" "table_row"
And I am on "Course 1" course homepage
And I click on "Add an activity or resource" "button" in the "New section" "section"
And I should see "Course Tool 1" in the ".modal-body" "css_element"
And I should not see "Course Tool 2" in the ".modal-body" "css_element"
When the following "role capability" exists:
| role | editingteacher |
| mod/lti:addcoursetool | prohibit |
And I am on "Course 1" course homepage with editing mode on
And I navigate to "LTI External tools" in current page administration
Then the "Don't show in activity chooser" "field" should be disabled
And the "Show in activity chooser" "field" should be disabled
@javascript
Scenario: Site and course tools settings are preserved when backup and restore
Given the following "mod_lti > tool types" exist:
| name | baseurl | coursevisible | state |
| Teaching Tool 1 | /mod/lti/tests/fixtures/tool_provider.php | 2 | 1 |
| Teaching Tool 2 | /mod/lti/tests/fixtures/tool_provider.php | 1 | 1 |
And the following "mod_lti > course tools" exist:
| name | description | baseurl | course |
| Course Tool 1 | Example description | https://example.com/tool | C1 |
And I log in as "admin"
And I am on "Course 1" course homepage with editing mode on
And I add a "Teaching Tool 1" to section "1" using the activity chooser
And I set the field "Activity name" to "Test tool activity 1"
And I press "Save and return to course"
And I add a "Course Tool 1" to section "1" using the activity chooser
And I set the field "Activity name" to "Course tool activity 1"
And I press "Save and return to course"
And I navigate to "LTI External tools" in current page administration
And I click on "Don't show in activity chooser" "field" in the "Teaching Tool 1" "table_row"
And I click on "Show in activity chooser" "field" in the "Teaching Tool 2" "table_row"
And I click on "Don't show in activity chooser" "field" in the "Course Tool 1" "table_row"
And I am on "Course 1" course homepage
And I add a "Teaching Tool 2" to section "1" using the activity chooser
And I set the field "Activity name" to "Test tool activity 2"
And I press "Save and return to course"
When I backup "Course 1" course using this options:
| Confirmation | Filename | test_backup.mbz |
And I restore "test_backup.mbz" backup into a new course using this options:
| Schema | Course name | Restored course |
And I should see "Restored course"
And I click on "Add an activity or resource" "button" in the "General" "section"
Then I should not see "Teaching Tool 1" in the ".modal-body" "css_element"
And I should see "Teaching Tool 2" in the ".modal-body" "css_element"
And I should not see "Course Tool 2" in the ".modal-body" "css_element"
And I click on "Close" "button" in the ".modal-dialog" "css_element"
And I navigate to "LTI External tools" in current page administration
And I should see "Show in activity chooser" in the "Teaching Tool 1" "table_row"
And I should see "Don't show in activity chooser" in the "Teaching Tool 2" "table_row"
And I should see "Show in activity chooser" in the "Course Tool 1" "table_row"
+22
View File
@@ -0,0 +1,22 @@
@mod @mod_lti
Feature: Rename external tools via inline editing
In order to keep track of my activities
As a teacher
I need to be able to rename the LTI tool and have it's name change in the gradebook
Background:
Given the following "courses" exist:
| fullname | shortname | category |
| Course 1 | C1 | 0 |
And the following "activities" exist:
| activity | course | name |
| lti | C1 | Test tool activity 1 |
@javascript
Scenario: Add a tool and inline edit
When I log in as "admin"
And I am on "Course 1" course homepage with editing mode on
And I set the field "Edit title" in the "Test tool activity 1" "activity" to "Test tool activity renamed"
And I navigate to "Setup > Gradebook setup" in the course gradebook
Then I should not see "Test tool activity 1"
And I should see "Test tool activity renamed"
@@ -0,0 +1,114 @@
@mod @mod_lti
Feature: Make an LTI only available to specific course categories
In order to restrict which courses a tool can be used in
As an administrator
I need to be able to select which course category the tool is available in
Background:
Given the following "users" exist:
| username | firstname | lastname | email |
| teacher1 | Terry1 | Teacher1 | teacher1@example.com |
And the following "categories" exist:
| name | category | idnumber |
| cata | 0 | cata |
| catca | cata | catca |
| catb | 0 | catb |
| catcb | catb | catcb |
And the following "courses" exist:
| fullname | shortname | category |
| Course 1 | C1 | cata |
| Course 2 | C2 | catb |
| Course 3 | C3 | catca |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
| teacher1 | C2 | editingteacher |
| teacher1 | C3 | editingteacher |
And the following "mod_lti > tool types" exist:
| name | description | baseurl | coursevisible | state | lti_coursecategories |
| Teaching Tool 1 | Tool 1 description | /mod/lti/tests/fixtures/tool_provider.php | 1 | 1 | catb |
| Teaching Tool 2 | Tool 2 description | /mod/lti/tests/fixtures/tool_provider.php | 2 | 1 | catca |
Scenario: Tool is set to "Show as preconfigured tool when adding an external tool" on parent category
Given I am on the "Course 2" course page logged in as teacher1
When I navigate to "LTI External tools" in current page administration
Then I should see "Teaching Tool 1" in the "reportbuilder-table" "table"
And I should not see "Teaching Tool 2" in the "reportbuilder-table" "table"
@javascript
Scenario: Tool is set to "Show in activity chooser and as preconfigured tool" on child category
Given I log in as "teacher1"
When I am on "Course 3" course homepage with editing mode on
And I open the activity chooser
Then I should see "Teaching Tool 2" in the "Add an activity or resource" "dialogue"
And I should not see "Teaching Tool 1" in the "Add an activity or resource" "dialogue"
@javascript
Scenario: View a course in a category in which no tools are available
Given I log in as "teacher1"
When I am on "Course 1" course homepage with editing mode on
And I open the activity chooser
Then I should not see "Teaching Tool 1" in the "Add an activity or resource" "dialogue"
And I should not see "Teaching Tool 2" in the "Add an activity or resource" "dialogue"
@javascript
Scenario: Editing and saving selected parent / child categories
Given I log in as "admin"
And I navigate to "Plugins > Activity modules > External tool > Manage tools" in site administration
And I follow "Manage preconfigured tools"
And I follow "Add preconfigured tool"
And I expand all fieldsets
And I click on "catb" "link"
And I set the following fields to these values:
| Tool name | Teaching Tool 3 |
| Tool configuration usage | Do not show; use only when a matching tool URL is entered |
| catb | 1 |
# If parent is selected, child should be selected.
And the field "catcb" matches value "1"
# If parent is unselected, child should be unselected.
And I set the following fields to these values:
| catb | 0 |
And the field "catcb" matches value "0"
# If parent is selected, child is unselected, parent should still be selected.
# Step 1 - Select parent first so child is selected.
And I set the following fields to these values:
| catb | 1 |
And the field "catcb" matches value "1"
# Step 2 - Unselect child but parent should stay as selected.
And I set the following fields to these values:
| catcb | 0 |
And the field "catb" matches value "1"
And I set the field "Tool URL" to local url "/mod/lti/tests/fixtures/tool_provider.php"
And I press "Save changes"
And I wait until the page is ready
And I should see "Teaching Tool 3"
When I click on "Update" "link" in the "Teaching Tool 3" "table_row"
And I expand all fieldsets
Then the following fields match these values:
| catb | 1 |
| catcb | 0 |
@javascript
Scenario: Category restriction only shown for a site tool
Given the following "mod_lti > tool types" exist:
| name | baseurl | coursevisible | state |
| Teaching Tool 1 | /mod/lti/tests/fixtures/tool_provider.php | 2 | 1 |
And the following "mod_lti > course tools" exist:
| name | description | baseurl | course |
| Course Tool 1 | Example description | https://example.com/tool | C1 |
And I log in as "admin"
And I am on "Course 1" course homepage with editing mode on
And I navigate to "LTI External tools" in current page administration
When I click on "Add tool" "link"
And I should not see "Restrict to category"
And I press "Cancel"
And I open the action menu in "Course Tool 1" "table_row"
And I choose "Edit" in the open action menu
And I should not see "Restrict to category"
And I navigate to "Plugins > Activity modules > External tool > Manage tools" in site administration
And I follow "Manage preconfigured tools"
And I follow "Add preconfigured tool"
And I should see "Restrict to category"
And I press "Cancel"
And I click on "Update" "link" in the "Teaching Tool 1" "table_row"
Then I should see "Restrict to category"
@@ -0,0 +1,40 @@
@mod @mod_lti
Feature: Verify the breadcrumbs in manage tools site administration pages
Whenever I navigate to manage tools page in site administration
As an admin
The breadcrumbs should be visible
Background:
Given I log in as "admin"
Scenario: Verify the breadcrumbs in manage tools page as an admin
Given I navigate to "Plugins > Activity modules > External tool > Manage tools" in site administration
And "Manage tools" "text" should exist in the ".breadcrumb" "css_element"
And "External tool" "link" should exist in the ".breadcrumb" "css_element"
And "Activity modules" "link" should exist in the ".breadcrumb" "css_element"
When I click on "configure a tool manually" "link"
Then "External tool configuration" "text" should exist in the ".breadcrumb" "css_element"
And "Manage tools" "link" should exist in the ".breadcrumb" "css_element"
And "External tool" "link" should exist in the ".breadcrumb" "css_element"
And "Activity modules" "link" should exist in the ".breadcrumb" "css_element"
And I press "Cancel"
And I click on "Manage preconfigured tools" "link"
And "Manage preconfigured tools" "text" should exist in the ".breadcrumb" "css_element"
And "External tool" "link" should exist in the ".breadcrumb" "css_element"
And "Activity modules" "link" should exist in the ".breadcrumb" "css_element"
And I click on "Add preconfigured tool" "link"
And "External tool configuration" "text" should exist in the ".breadcrumb" "css_element"
And "Manage tools" "link" should exist in the ".breadcrumb" "css_element"
And "External tool" "link" should exist in the ".breadcrumb" "css_element"
And "Activity modules" "link" should exist in the ".breadcrumb" "css_element"
And I press "Cancel"
And I navigate to "Plugins > Activity modules > External tool > Manage tools" in site administration
And I click on "Manage external tool registrations" "link"
And "Manage external tool registrations" "text" should exist in the ".breadcrumb" "css_element"
And "External tool" "link" should exist in the ".breadcrumb" "css_element"
And "Activity modules" "link" should exist in the ".breadcrumb" "css_element"
And I click on "Configure a new external tool registration" "link"
And "Edit preconfigured tool" "text" should exist in the ".breadcrumb" "css_element"
And "Manage external tool registrations" "link" should exist in the ".breadcrumb" "css_element"
And "External tool" "link" should exist in the ".breadcrumb" "css_element"
And "Activity modules" "link" should exist in the ".breadcrumb" "css_element"
+32
View File
@@ -0,0 +1,32 @@
@mod @mod_lti
Feature: Configure tool types
In order to allow teachers to add external LTI tools
As an admin
I need to be able to add, remove and configure tool types
Background:
Given I log in as "admin"
And I navigate to "Plugins > Activity modules > External tool > Manage tools" in site administration
@javascript
Scenario: Add a tool type from a cartridge URL
When I set the field "url" to local url "/mod/lti/tests/fixtures/ims_cartridge_basic_lti_link.xml"
And I press "Add Legacy LTI"
Then I should see "Enter your consumer key and shared secret"
And I press "Save changes"
And I should see "Example tool"
@javascript
Scenario: Try to add a non-existant cartridge
When I set the field "url" to local url "/mod/lti/tests/fixtures/nonexistant.xml"
And I press "Add Legacy LTI"
Then I should see "Enter your consumer key and shared secret"
And I press "Save changes"
And I should see "Failed to create new tool. Please check the URL and try again."
@javascript
Scenario: Attempt to add a tool type from a configuration URL, then cancel
When I set the field "url" to local url "/mod/lti/tests/fixtures/tool_provider.php"
And I press "Add Legacy LTI"
Then I should see "Cancel"
And I press "cancel-external-registration"
+30
View File
@@ -0,0 +1,30 @@
@mod @mod_lti
Feature: Navigate existing LTI tool types using pagination
In order to manage reusable activities for teachers
As an admin
I need to view existing tools
Background:
Given 100 "mod_lti > tool types" exist with the following data:
|name |Test tool [count] |
|description |Example description [count] |
|baseurl |https://www.example.com/tool[count]|
And I log in as "admin"
And I navigate to "Plugins > Activity modules > External tool > Manage tools" in site administration
@javascript
Scenario: View first page of tool types.
Then I should see "Test tool 30"
And "Test tool 70" "text" should not be visible
@javascript
Scenario: View second page of tool types using page 2 button.
When I click on "2" "link"
Then I should see "Test tool 70"
And "Test tool 30" "text" should not be visible
@javascript
Scenario: View last page of tool types using page 2 button.
When I click on "Last" "link"
Then I should see "Test tool 70"
And "Test tool 30" "text" should not be visible
+58
View File
@@ -0,0 +1,58 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Helpers for restrict course categories unit tests.
*
* @package mod_lti
* @copyright 2023 Jackson D'Souza <jackson.dsouza@catalyst-eu.net>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since Moodle 4.3
*/
trait mod_lti_course_categories_trait {
/**
* Setup course categories.
*
* @return array
*/
public function setup_course_categories(): array {
global $DB;
$topcatdbrecord = $DB->get_record('course_categories', ['parent' => 0]);
$subcata = $this->getDataGenerator()->create_category(['parent' => $topcatdbrecord->id, 'name' => 'cata']);
$subcatadbrecord = $DB->get_record('course_categories', ['id' => $subcata->id]);
$subcatca = $this->getDataGenerator()->create_category(['parent' => $subcata->id, 'name' => 'catca']);
$subcatcadbrecord = $DB->get_record('course_categories', ['id' => $subcatca->id]);
$subcatb = $this->getDataGenerator()->create_category(['parent' => $topcatdbrecord->id, 'name' => 'catb']);
$subcatbdbrecord = $DB->get_record('course_categories', ['id' => $subcatb->id]);
$subcatcb = $this->getDataGenerator()->create_category(['parent' => $subcatb->id, 'name' => 'catcb']);
$subcatcbdbrecord = $DB->get_record('course_categories', ['id' => $subcatcb->id]);
return [
'topcat' => $topcatdbrecord,
'subcata' => $subcatadbrecord,
'subcatca' => $subcatcadbrecord,
'subcatb' => $subcatb,
'subcatcb' => $subcatcbdbrecord
];
}
}
@@ -0,0 +1,71 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace mod_lti\event;
/**
* Unknown service API called event tests
*
* @package mod_lti
* @copyright Copyright (c) 2012 Moodlerooms Inc. (http://www.moodlerooms.com)
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class unknown_service_api_called_test extends \advanced_testcase {
/*
* Ensure create event works.
*/
public function test_create_event(): void {
$event = unknown_service_api_called::create();
$this->assertInstanceOf('\mod_lti\event\unknown_service_api_called', $event);
}
/*
* Ensure event context works.
*/
public function test_event_context(): void {
$event = unknown_service_api_called::create();
$this->assertEquals(\context_system::instance(), $event->get_context());
}
/*
* Ensure we can trigger the event.
*/
public function test_trigger_event(): void {
$event = unknown_service_api_called::create();
$sink = $this->redirectEvents();
$event->trigger();
$events = $sink->get_events();
$this->assertCount(1, $events);
}
/*
* Ensure get/set message data is functioning as expected.
*/
public function test_get_message_data(): void {
$data = (object) array(
'foo' => 'bar',
'bat' => 'baz',
);
/*
* @var unknown_service_api_called $event
*/
$event = unknown_service_api_called::create();
$event->set_message_data($data);
$this->assertSame($data, $event->get_message_data());
}
}
+82
View File
@@ -0,0 +1,82 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace mod_lti\external;
use core_external\external_api;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->dirroot . '/mod/lti/tests/mod_lti_testcase.php');
/**
* PHPUnit tests for delete_course_tool_type external function.
*
* @package mod_lti
* @copyright 2023 Jake Dallimore <jrhdallimore@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @coversDefaultClass \mod_lti\external\delete_course_tool_type
*/
class delete_course_tool_type_test extends \mod_lti_testcase {
/**
* Test delete_course_tool() for a course tool.
* @covers ::execute
*/
public function test_delete_course_tool(): void {
$this->resetAfterTest();
$course = $this->getDataGenerator()->create_course();
$editingteacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
$this->setUser($editingteacher);
$typeid = lti_add_type(
(object) [
'state' => LTI_TOOL_STATE_CONFIGURED,
'course' => $course->id
],
(object) [
'lti_typename' => "My course tool",
'lti_toolurl' => 'http://example.com',
'lti_ltiversion' => 'LTI-1p0'
]
);
$data = delete_course_tool_type::execute($typeid);
$data = external_api::clean_returnvalue(delete_course_tool_type::execute_returns(), $data);
$this->assertTrue($data);
}
/**
* Test delete_course_tool() for a site tool, which is forbidden.
* @covers ::execute
*/
public function test_delete_course_tool_site_tool(): void {
$this->resetAfterTest();
$course = $this->getDataGenerator()->create_course();
$editingteacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
$this->setUser($editingteacher);
$type = $this->generate_tool_type(123); // Creates a site tool.
$this->expectException(\invalid_parameter_exception::class);
delete_course_tool_type::execute($type->id);
}
}
@@ -0,0 +1,69 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace mod_lti\external;
use core_external\external_api;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->dirroot . '/mod/lti/tests/mod_lti_testcase.php');
/**
* PHPUnit tests for get_tool_types_and_proxies_count external function.
*
* @package mod_lti
* @author Andrew Madden <andrewmadden@catalyst-au.net>
* @copyright 2021 Catalyst IT
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class get_tool_types_and_proxies_count_test extends \mod_lti_testcase {
/**
* This method runs before every test.
*/
public function setUp(): void {
$this->resetAfterTest();
$this->setAdminUser();
}
/**
* Test get_tool_types_and_proxies_count returns the correct number.
*/
public function test_mod_lti_get_tool_types_and_proxies_count(): void {
for ($i = 0; $i < 10; $i++) {
$proxy = $this->generate_tool_proxy($i);
$this->generate_tool_type($i, $proxy->id);
}
$data = \mod_lti\external\get_tool_types_and_proxies_count::execute(0, false);
$data = external_api::clean_returnvalue(\mod_lti\external\get_tool_types_and_proxies_count::execute_returns(), $data);
$this->assertEquals(20, $data['count']);
}
/**
* Test get_tool_types_and_proxies_count returns the correct number.
*/
public function test_mod_lti_get_tool_types_and_proxies_count_with_no_tools_configured(): void {
$data = \mod_lti\external\get_tool_types_and_proxies_count::execute(0, false);
$data = external_api::clean_returnvalue(\mod_lti\external\get_tool_types_and_proxies_count::execute_returns(), $data);
$this->assertEquals(0, $data['count']);
}
}
@@ -0,0 +1,117 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace mod_lti\external;
use core_external\external_api;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->dirroot . '/mod/lti/tests/mod_lti_testcase.php');
/**
* PHPUnit tests for get_tool_types_and_proxies external function.
*
* @package mod_lti
* @author Andrew Madden <andrewmadden@catalyst-au.net>
* @copyright 2021 Catalyst IT
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class get_tool_types_and_proxies_test extends \mod_lti_testcase {
/**
* This method runs before every test.
*/
public function setUp(): void {
$this->resetAfterTest();
$this->setAdminUser();
}
/**
* Test get_tool_types_and_proxies.
*/
public function test_mod_lti_get_tool_types_and_proxies(): void {
$proxy = $this->generate_tool_proxy(1);
$this->generate_tool_type(1, $proxy->id);
$data = get_tool_types_and_proxies::execute(0, false, 50, 0);
$data = external_api::clean_returnvalue(get_tool_types_and_proxies::execute_returns(), $data);
$this->assertCount(1, $data['types']);
$type = $data['types'][0];
$this->assertEquals('Test tool 1', $type['name']);
$this->assertEquals('Example description 1', $type['description']);
$this->assertCount(1, $data['proxies']);
$proxy = $data['proxies'][0];
$this->assertEquals('Test proxy 1', $proxy['name']);
$this->assertEquals(50, $data['limit']);
$this->assertEquals(0, $data['offset']);
}
/**
* Test get_tool_types_and_proxies with multiple pages of tool types.
*/
public function test_mod_lti_get_tool_types_and_proxies_with_multiple_pages(): void {
for ($i = 0; $i < 3; $i++) {
$proxy = $this->generate_tool_proxy($i);
$this->generate_tool_type($i, $proxy->id);
}
$data = get_tool_types_and_proxies::execute(0, false, 5, 0);
$data = external_api::clean_returnvalue(get_tool_types_and_proxies::execute_returns(), $data);
$this->assertCount(2, $data['types']);
$this->assertCount(3, $data['proxies']);
$this->assertEquals(5, $data['limit']);
$this->assertEquals(0, $data['offset']);
}
/**
* Test get_tool_types_and_proxies with multiple pages of tool types and offset.
*/
public function test_mod_lti_get_tool_types_and_proxies_with_multiple_pages_last_page(): void {
for ($i = 0; $i < 6; $i++) {
$proxy = $this->generate_tool_proxy($i);
$this->generate_tool_type($i, $proxy->id);
}
$data = get_tool_types_and_proxies::execute(0, false, 5, 10);
$data = external_api::clean_returnvalue(get_tool_types_and_proxies::execute_returns(), $data);
$this->assertCount(2, $data['types']);
$this->assertCount(0, $data['proxies']);
$this->assertEquals(5, $data['limit']);
$this->assertEquals(10, $data['offset']);
}
/**
* Test get_tool_types_and_proxies without pagination.
*/
public function test_mod_lti_get_tool_types_and_proxies_without_pagination(): void {
for ($i = 0; $i < 10; $i++) {
$proxy = $this->generate_tool_proxy($i);
$this->generate_tool_type($i, $proxy->id);
}
$data = get_tool_types_and_proxies::execute(0, false, 0, 0);
$data = external_api::clean_returnvalue(get_tool_types_and_proxies::execute_returns(), $data);
$this->assertCount(10, $data['types']);
$this->assertCount(10, $data['proxies']);
}
}
@@ -0,0 +1,162 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace mod_lti\external;
use core_external\external_api;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->dirroot . '/mod/lti/tests/mod_lti_testcase.php');
/**
* PHPUnit tests for toggle_showinactivitychooser external function.
*
* @package mod_lti
* @copyright 2023 Ilya Tregubov <ilya.a.tregubov@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @coversDefaultClass \mod_lti\external\toggle_showinactivitychooser
*/
class toggle_showinactivitychooser_test extends \mod_lti_testcase {
/**
* Test toggle_showinactivitychooser for course tool.
* @covers ::execute
*/
public function test_toggle_showinactivitychooser_course_tool(): void {
global $DB;
$this->resetAfterTest();
$course = $this->getDataGenerator()->create_course();
$editingteacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
$this->setUser($editingteacher);
$typeid = lti_add_type(
(object) [
'state' => LTI_TOOL_STATE_CONFIGURED,
'course' => $course->id,
'coursevisible' => LTI_COURSEVISIBLE_ACTIVITYCHOOSER
],
(object) [
'lti_typename' => "My course tool",
'lti_toolurl' => 'http://example.com',
'lti_ltiversion' => 'LTI-1p0',
'lti_coursevisible' => LTI_COURSEVISIBLE_ACTIVITYCHOOSER
]
);
$result = toggle_showinactivitychooser::execute($typeid, $course->id, false);
$result = external_api::clean_returnvalue(toggle_showinactivitychooser::execute_returns(), $result);
$this->assertTrue($result);
$sql = "SELECT lt.coursevisible coursevisible
FROM {lti_types} lt
WHERE lt.id = ?";
$actual = $DB->get_record_sql($sql, [$typeid]);
$this->assertEquals(LTI_COURSEVISIBLE_PRECONFIGURED, $actual->coursevisible);
$result = toggle_showinactivitychooser::execute($typeid, $course->id, true);
$result = external_api::clean_returnvalue(toggle_showinactivitychooser::execute_returns(), $result);
$this->assertTrue($result);
$actual = $DB->get_record_sql($sql, [$typeid]);
$this->assertEquals(LTI_COURSEVISIBLE_ACTIVITYCHOOSER, $actual->coursevisible);
}
/**
* Test toggle_showinactivitychooser for site tool.
* @covers ::execute
*/
public function test_toggle_showinactivitychooser_site_tool(): void {
global $DB;
$this->resetAfterTest();
$coursecat1 = $this->getDataGenerator()->create_category();
$coursecat2 = $this->getDataGenerator()->create_category();
$course = $this->getDataGenerator()->create_course(['category' => $coursecat1->id]);
$editingteacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
$this->setUser($editingteacher);
$type = $this->generate_tool_type(123); // Creates a site tool.
$result = toggle_showinactivitychooser::execute($type->id, $course->id, false);
$result = external_api::clean_returnvalue(toggle_showinactivitychooser::execute_returns(), $result);
$this->assertTrue($result);
$sql = "SELECT lt.coursevisible coursevisible1, lc.coursevisible AS coursevisible2
FROM {lti_types} lt
LEFT JOIN {lti_coursevisible} lc ON lt.id = lc.typeid
WHERE lt.id = ?
AND lc.courseid = ?";
$actual = $DB->get_record_sql($sql, [$type->id, $course->id]);
$this->assertEquals(LTI_COURSEVISIBLE_ACTIVITYCHOOSER, $actual->coursevisible1);
$this->assertEquals(LTI_COURSEVISIBLE_PRECONFIGURED, $actual->coursevisible2);
$result = toggle_showinactivitychooser::execute($type->id, $course->id, true);
$result = external_api::clean_returnvalue(toggle_showinactivitychooser::execute_returns(), $result);
$this->assertTrue($result);
$actual = $DB->get_record_sql($sql, [$type->id, $course->id]);
$this->assertEquals(LTI_COURSEVISIBLE_ACTIVITYCHOOSER, $actual->coursevisible1);
$this->assertEquals(LTI_COURSEVISIBLE_ACTIVITYCHOOSER, $actual->coursevisible2);
$ltigenerator = $this->getDataGenerator()->get_plugin_generator('mod_lti');
$ltigenerator->create_tool_types([
'name' => 'site tool preconfigured and activity chooser, restricted to category 1',
'baseurl' => 'http://example.com/tool/1',
'coursevisible' => LTI_COURSEVISIBLE_ACTIVITYCHOOSER,
'state' => LTI_TOOL_STATE_CONFIGURED,
'lti_coursecategories' => $coursecat1->id
]);
$tool = $DB->get_record('lti_types', ['name' => 'site tool preconfigured and activity chooser, restricted to category 1']);
$result = toggle_showinactivitychooser::execute($tool->id, $course->id, false);
$result = external_api::clean_returnvalue(toggle_showinactivitychooser::execute_returns(), $result);
$this->assertTrue($result);
$actual = $DB->get_record_sql($sql, [$tool->id, $course->id]);
$this->assertEquals(LTI_COURSEVISIBLE_ACTIVITYCHOOSER, $actual->coursevisible1);
$this->assertEquals(LTI_COURSEVISIBLE_PRECONFIGURED, $actual->coursevisible2);
$ltigenerator = $this->getDataGenerator()->get_plugin_generator('mod_lti');
$ltigenerator->create_tool_types([
'name' => 'site tool preconfigured and activity chooser, restricted to category 2',
'baseurl' => 'http://example.com/tool/1',
'coursevisible' => LTI_COURSEVISIBLE_ACTIVITYCHOOSER,
'state' => LTI_TOOL_STATE_CONFIGURED,
'lti_coursecategories' => $coursecat2->id
]);
$tool = $DB->get_record('lti_types', ['name' => 'site tool preconfigured and activity chooser, restricted to category 2']);
$this->expectException('moodle_exception');
$this->expectExceptionMessage('You are not allowed to change this setting for this tool.');
toggle_showinactivitychooser::execute($tool->id, $course->id, true);
$ltigenerator = $this->getDataGenerator()->get_plugin_generator('mod_lti');
$ltigenerator->create_tool_types([
'name' => 'site tool dont show',
'baseurl' => 'http://example.com/tool/1',
'coursevisible' => LTI_COURSEVISIBLE_NO,
'state' => LTI_TOOL_STATE_CONFIGURED,
]);
$tool = $DB->get_record('lti_types', ['name' => 'site tool dont show']);
$result = toggle_showinactivitychooser::execute($tool->id, $course->id, false);
$result = external_api::clean_returnvalue(toggle_showinactivitychooser::execute_returns(), $result);
$this->assertFalse($result);
}
}
+579
View File
@@ -0,0 +1,579 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace mod_lti;
use core_external\external_api;
use mod_lti_external;
use mod_lti_testcase;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->dirroot . '/webservice/tests/helpers.php');
require_once($CFG->dirroot . '/mod/lti/lib.php');
require_once($CFG->dirroot . '/mod/lti/tests/mod_lti_testcase.php');
/**
* External tool module external functions tests
*
* @package mod_lti
* @category external
* @copyright 2015 Juan Leyva <juan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since Moodle 3.0
*/
class externallib_test extends mod_lti_testcase {
/**
* Set up for every test
*/
public function setUp(): void {
$this->resetAfterTest();
}
/**
* Sets up some basic test data including course, users, roles, and an lti instance, for use in some tests.
* @return array
*/
protected function setup_test_data() {
global $DB;
$this->setAdminUser();
// Setup test data.
$course = $this->getDataGenerator()->create_course();
$lti = $this->getDataGenerator()->create_module(
'lti',
['course' => $course->id, 'toolurl' => 'http://localhost/not/real/tool.php']
);
$context = \context_module::instance($lti->cmid);
$cm = get_coursemodule_from_instance('lti', $lti->id);
// Create users.
$student = self::getDataGenerator()->create_user();
$teacher = self::getDataGenerator()->create_user();
// Users enrolments.
$studentrole = $DB->get_record('role', array('shortname' => 'student'));
$teacherrole = $DB->get_record('role', array('shortname' => 'editingteacher'));
$this->getDataGenerator()->enrol_user($student->id, $course->id, $studentrole->id, 'manual');
$this->getDataGenerator()->enrol_user($teacher->id, $course->id, $teacherrole->id, 'manual');
return [
'course' => $course,
'lti' => $lti,
'context' => $context,
'cm' => $cm,
'student' => $student,
'teacher' => $teacher,
'studentrole' => $studentrole,
'teacherrole' => $teacherrole
];
}
/**
* Test get_tool_proxies.
*/
public function test_mod_lti_get_tool_proxies(): void {
// Create two tool proxies. One to associate with tool, and one to leave orphaned.
$this->setAdminUser();
$proxy = $this->generate_tool_proxy("1");
$orphanedproxy = $this->generate_tool_proxy("2");
$this->generate_tool_type("1", $proxy->id); // Associate proxy 1 with tool type.
// Fetch all proxies.
$proxies = mod_lti_external::get_tool_proxies(false);
$proxies = external_api::clean_returnvalue(mod_lti_external::get_tool_proxies_returns(), $proxies);
$this->assertCount(2, $proxies);
$this->assertEqualsCanonicalizing([(array) $proxy, (array) $orphanedproxy], $proxies);
}
/**
* Test get_tool_proxies with orphaned proxies only.
*/
public function test_mod_lti_get_orphaned_tool_proxies(): void {
// Create two tool proxies. One to associate with tool, and one to leave orphaned.
$this->setAdminUser();
$proxy = $this->generate_tool_proxy("1");
$orphanedproxy = $this->generate_tool_proxy("2");
$this->generate_tool_type("1", $proxy->id); // Associate proxy 1 with tool type.
// Fetch all proxies.
$proxies = mod_lti_external::get_tool_proxies(true);
$proxies = external_api::clean_returnvalue(mod_lti_external::get_tool_proxies_returns(), $proxies);
$this->assertCount(1, $proxies);
$this->assertEqualsCanonicalizing([(array) $orphanedproxy], $proxies);
}
/**
* Test get_tool_launch_data.
*/
public function test_get_tool_launch_data(): void {
global $USER;
[
'course' => $course,
'lti' => $lti
] = $this->setup_test_data();
$result = mod_lti_external::get_tool_launch_data($lti->id);
$result = external_api::clean_returnvalue(mod_lti_external::get_tool_launch_data_returns(), $result);
// Basic test, the function returns what it's expected.
self::assertEquals($lti->toolurl, $result['endpoint']);
self::assertCount(36, $result['parameters']);
// Check some parameters.
$parameters = array();
foreach ($result['parameters'] as $param) {
$parameters[$param['name']] = $param['value'];
}
self::assertEquals($lti->resourcekey, $parameters['oauth_consumer_key']);
self::assertEquals($course->fullname, $parameters['context_title']);
self::assertEquals($course->shortname, $parameters['context_label']);
self::assertEquals($USER->id, $parameters['user_id']);
self::assertEquals($USER->firstname, $parameters['lis_person_name_given']);
self::assertEquals($USER->lastname, $parameters['lis_person_name_family']);
self::assertEquals(fullname($USER), $parameters['lis_person_name_full']);
self::assertEquals($USER->username, $parameters['ext_user_username']);
self::assertEquals("phpunit", $parameters['tool_consumer_instance_name']);
self::assertEquals("PHPUnit test site", $parameters['tool_consumer_instance_description']);
}
/**
* Test get_ltis_by_courses.
*/
public function test_mod_lti_get_ltis_by_courses(): void {
[
'course' => $course,
'lti' => $lti,
'student' => $student,
'teacher' => $teacher,
'studentrole' => $studentrole,
] = $this->setup_test_data();
// Create additional course.
$course2 = self::getDataGenerator()->create_course();
// Second lti.
$record = new \stdClass();
$record->course = $course2->id;
$lti2 = self::getDataGenerator()->create_module('lti', $record);
// Execute real Moodle enrolment as we'll call unenrol() method on the instance later.
$enrol = enrol_get_plugin('manual');
$enrolinstances = enrol_get_instances($course2->id, true);
foreach ($enrolinstances as $courseenrolinstance) {
if ($courseenrolinstance->enrol == "manual") {
$instance2 = $courseenrolinstance;
break;
}
}
$enrol->enrol_user($instance2, $student->id, $studentrole->id);
self::setUser($student);
$returndescription = mod_lti_external::get_ltis_by_courses_returns();
// Create what we expect to be returned when querying the two courses.
// First for the student user.
$expectedfields = array('id', 'coursemodule', 'course', 'name', 'intro', 'introformat', 'introfiles', 'lang',
'launchcontainer', 'showtitlelaunch', 'showdescriptionlaunch', 'icon', 'secureicon');
// Add expected coursemodule and data.
$lti1 = $lti;
$lti1->coursemodule = $lti1->cmid;
$lti1->introformat = 1;
$lti1->section = 0;
$lti1->visible = true;
$lti1->groupmode = 0;
$lti1->groupingid = 0;
$lti1->section = 0;
$lti1->introfiles = [];
$lti1->lang = '';
$lti2->coursemodule = $lti2->cmid;
$lti2->introformat = 1;
$lti2->section = 0;
$lti2->visible = true;
$lti2->groupmode = 0;
$lti2->groupingid = 0;
$lti2->section = 0;
$lti2->introfiles = [];
$lti2->lang = '';
foreach ($expectedfields as $field) {
$expected1[$field] = $lti1->{$field};
$expected2[$field] = $lti2->{$field};
}
$expectedltis = array($expected2, $expected1);
// Call the external function passing course ids.
$result = mod_lti_external::get_ltis_by_courses(array($course2->id, $course->id));
$result = external_api::clean_returnvalue($returndescription, $result);
$this->assertEquals($expectedltis, $result['ltis']);
$this->assertCount(0, $result['warnings']);
// Call the external function without passing course id.
$result = mod_lti_external::get_ltis_by_courses();
$result = external_api::clean_returnvalue($returndescription, $result);
$this->assertEquals($expectedltis, $result['ltis']);
$this->assertCount(0, $result['warnings']);
// Unenrol user from second course and alter expected ltis.
$enrol->unenrol_user($instance2, $student->id);
array_shift($expectedltis);
// Call the external function without passing course id.
$result = mod_lti_external::get_ltis_by_courses();
$result = external_api::clean_returnvalue($returndescription, $result);
$this->assertEquals($expectedltis, $result['ltis']);
// Call for the second course we unenrolled the user from, expected warning.
$result = mod_lti_external::get_ltis_by_courses(array($course2->id));
$result = external_api::clean_returnvalue($returndescription, $result);
$this->assertCount(1, $result['warnings']);
$this->assertEquals('1', $result['warnings'][0]['warningcode']);
$this->assertEquals($course2->id, $result['warnings'][0]['itemid']);
// Now, try as a teacher for getting all the additional fields.
self::setUser($teacher);
$additionalfields = array('timecreated', 'timemodified', 'typeid', 'toolurl', 'securetoolurl',
'instructorchoicesendname', 'instructorchoicesendemailaddr', 'instructorchoiceallowroster',
'instructorchoiceallowsetting', 'instructorcustomparameters', 'instructorchoiceacceptgrades', 'grade',
'resourcekey', 'password', 'debuglaunch', 'servicesalt', 'visible', 'groupmode', 'groupingid', 'section', 'lang');
foreach ($additionalfields as $field) {
$expectedltis[0][$field] = $lti1->{$field};
}
$result = mod_lti_external::get_ltis_by_courses();
$result = external_api::clean_returnvalue($returndescription, $result);
$this->assertEquals($expectedltis, $result['ltis']);
// Admin also should get all the information.
self::setAdminUser();
$result = mod_lti_external::get_ltis_by_courses(array($course->id));
$result = external_api::clean_returnvalue($returndescription, $result);
$this->assertEquals($expectedltis, $result['ltis']);
// Now, prohibit capabilities.
$this->setUser($student);
$contextcourse1 = \context_course::instance($course->id);
// Prohibit capability = mod:lti:view on Course1 for students.
assign_capability('mod/lti:view', CAP_PROHIBIT, $studentrole->id, $contextcourse1->id);
// Empty all the caches that may be affected by this change.
accesslib_clear_all_caches_for_unit_testing();
\course_modinfo::clear_instance_cache();
$ltis = mod_lti_external::get_ltis_by_courses(array($course->id));
$ltis = external_api::clean_returnvalue(mod_lti_external::get_ltis_by_courses_returns(), $ltis);
$this->assertCount(0, $ltis['ltis']);
}
/**
* Test view_lti with an invalid instance id.
*/
public function test_view_lti_invalid_instanceid(): void {
$this->expectException(\moodle_exception::class);
mod_lti_external::view_lti(0);
}
/**
* Test view_lti as a user who is not enrolled in the course.
*/
public function test_view_lti_no_enrolment(): void {
[
'lti' => $lti
] = $this->setup_test_data();
// Test not-enrolled user.
$usernotenrolled = self::getDataGenerator()->create_user();
$this->setUser($usernotenrolled);
$this->expectException(\moodle_exception::class);
mod_lti_external::view_lti($lti->id);
}
/**
* Test view_lti for a user without the mod/lti:view capability.
*/
public function test_view_lti_no_capability(): void {
[
'lti' => $lti,
'student' => $student,
'studentrole' => $studentrole,
'context' => $context,
] = $this->setup_test_data();
$this->setUser($student);
// We need a explicit prohibit since this capability is only defined in authenticated user and guest roles.
assign_capability('mod/lti:view', CAP_PROHIBIT, $studentrole->id, $context->id);
// Empty all the caches that may be affected by this change.
accesslib_clear_all_caches_for_unit_testing();
\course_modinfo::clear_instance_cache();
$this->expectException(\moodle_exception::class);
mod_lti_external::view_lti($lti->id);
}
/**
* Test view_lti for a user with the mod/lti:view capability in the course.
*/
public function test_view_lti(): void {
[
'lti' => $lti,
'context' => $context,
'cm' => $cm,
'student' => $student,
] = $this->setup_test_data();
// Test user with full capabilities.
$this->setUser($student);
// Trigger and capture the event.
$sink = $this->redirectEvents();
$result = mod_lti_external::view_lti($lti->id);
// The value of the result isn't needed but validation is.
external_api::clean_returnvalue(mod_lti_external::view_lti_returns(), $result);
$events = $sink->get_events();
$this->assertCount(1, $events);
$event = array_shift($events);
// Checking that the event contains the expected values.
$this->assertInstanceOf('\mod_lti\event\course_module_viewed', $event);
$this->assertEquals($context, $event->get_context());
$moodlelti = new \moodle_url('/mod/lti/view.php', array('id' => $cm->id));
$this->assertEquals($moodlelti, $event->get_url());
$this->assertEventContextNotUsed($event);
$this->assertNotEmpty($event->get_name());
}
/**
* Test create_tool_proxy.
*/
public function test_mod_lti_create_tool_proxy(): void {
$this->setAdminUser();
$capabilities = ['AA', 'BB'];
$proxy = mod_lti_external::create_tool_proxy('Test proxy', $this->getExternalTestFileUrl('/test.html'), $capabilities, []);
$proxy = (object) external_api::clean_returnvalue(mod_lti_external::create_tool_proxy_returns(), $proxy);
$this->assertEquals('Test proxy', $proxy->name);
$this->assertEquals($this->getExternalTestFileUrl('/test.html'), $proxy->regurl);
$this->assertEquals(LTI_TOOL_PROXY_STATE_PENDING, $proxy->state);
$this->assertEquals(implode("\n", $capabilities), $proxy->capabilityoffered);
}
/**
* Test create_tool_proxy with a duplicate url.
*/
public function test_mod_lti_create_tool_proxy_duplicateurl(): void {
$this->setAdminUser();
mod_lti_external::create_tool_proxy('Test proxy 1', $this->getExternalTestFileUrl('/test.html'), array(), array());
$this->expectException(\moodle_exception::class);
mod_lti_external::create_tool_proxy('Test proxy 2', $this->getExternalTestFileUrl('/test.html'), array(), array());
}
/**
* Test create_tool_proxy for a user without the required capability.
*/
public function test_mod_lti_create_tool_proxy_without_capability(): void {
$course = $this->getDataGenerator()->create_course();
$teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
$this->setUser($teacher);
$this->expectException(\required_capability_exception::class);
mod_lti_external::create_tool_proxy('Test proxy', $this->getExternalTestFileUrl('/test.html'), array(), array());
}
/**
* Test delete_tool_proxy.
*/
public function test_mod_lti_delete_tool_proxy(): void {
$this->setAdminUser();
$proxy = mod_lti_external::create_tool_proxy('Test proxy', $this->getExternalTestFileUrl('/test.html'), array(), array());
$proxy = (object) external_api::clean_returnvalue(mod_lti_external::create_tool_proxy_returns(), $proxy);
$this->assertNotEmpty(lti_get_tool_proxy($proxy->id));
$proxy = mod_lti_external::delete_tool_proxy($proxy->id);
$proxy = (object) external_api::clean_returnvalue(mod_lti_external::delete_tool_proxy_returns(), $proxy);
$this->assertEquals('Test proxy', $proxy->name);
$this->assertEquals($this->getExternalTestFileUrl('/test.html'), $proxy->regurl);
$this->assertEquals(LTI_TOOL_PROXY_STATE_PENDING, $proxy->state);
$this->assertEmpty(lti_get_tool_proxy($proxy->id));
}
/**
* Test get_tool_proxy_registration_request.
*/
public function test_mod_lti_get_tool_proxy_registration_request(): void {
$this->setAdminUser();
$proxy = mod_lti_external::create_tool_proxy('Test proxy', $this->getExternalTestFileUrl('/test.html'), array(), array());
$proxy = (object) external_api::clean_returnvalue(mod_lti_external::create_tool_proxy_returns(), $proxy);
$request = mod_lti_external::get_tool_proxy_registration_request($proxy->id);
$request = external_api::clean_returnvalue(mod_lti_external::get_tool_proxy_registration_request_returns(),
$request);
$this->assertEquals('ToolProxyRegistrationRequest', $request['lti_message_type']);
$this->assertEquals('LTI-2p0', $request['lti_version']);
}
/**
* Test get_tool_types.
*/
public function test_mod_lti_get_tool_types(): void {
$this->setAdminUser();
$proxy = mod_lti_external::create_tool_proxy('Test proxy', $this->getExternalTestFileUrl('/test.html'), array(), array());
$proxy = (object) external_api::clean_returnvalue(mod_lti_external::create_tool_proxy_returns(), $proxy);
// Create a tool type, associated with that proxy.
$type = new \stdClass();
$data = new \stdClass();
$type->state = LTI_TOOL_STATE_CONFIGURED;
$type->name = "Test tool";
$type->description = "Example description";
$type->toolproxyid = $proxy->id;
$type->baseurl = $this->getExternalTestFileUrl('/test.html');
lti_add_type($type, $data);
$types = mod_lti_external::get_tool_types($proxy->id);
$types = external_api::clean_returnvalue(mod_lti_external::get_tool_types_returns(), $types);
$this->assertCount(1, $types);
$type = $types[0];
$this->assertEquals('Test tool', $type['name']);
$this->assertEquals('Example description', $type['description']);
}
/**
* Test create_tool_type.
*/
public function test_mod_lti_create_tool_type(): void {
$this->setAdminUser();
$type = mod_lti_external::create_tool_type($this->getExternalTestFileUrl('/ims_cartridge_basic_lti_link.xml'), '', '');
$type = external_api::clean_returnvalue(mod_lti_external::create_tool_type_returns(), $type);
$this->assertEquals('Example tool', $type['name']);
$this->assertEquals('Example tool description', $type['description']);
$this->assertEquals('https://download.moodle.org/unittest/test.jpg', $type['urls']['icon']);
$typeentry = lti_get_type($type['id']);
$this->assertEquals('http://www.example.com/lti/provider.php', $typeentry->baseurl);
$config = lti_get_type_config($type['id']);
$this->assertTrue(isset($config['sendname']));
$this->assertTrue(isset($config['sendemailaddr']));
$this->assertTrue(isset($config['acceptgrades']));
$this->assertTrue(isset($config['forcessl']));
}
/**
* Test create_tool_type failure from non existent file.
*/
public function test_mod_lti_create_tool_type_nonexistant_file(): void {
$this->expectException(\moodle_exception::class);
mod_lti_external::create_tool_type($this->getExternalTestFileUrl('/doesntexist.xml'), '', '');
}
/**
* Test create_tool_type failure from xml that is not a cartridge.
*/
public function test_mod_lti_create_tool_type_bad_file(): void {
$this->expectException(\moodle_exception::class);
mod_lti_external::create_tool_type($this->getExternalTestFileUrl('/rsstest.xml'), '', '');
}
/**
* Test create_tool_type as a user without the required capability.
*/
public function test_mod_lti_create_tool_type_without_capability(): void {
$course = $this->getDataGenerator()->create_course();
$teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
$this->setUser($teacher);
$this->expectException(\required_capability_exception::class);
mod_lti_external::create_tool_type($this->getExternalTestFileUrl('/ims_cartridge_basic_lti_link.xml'), '', '');
}
/**
* Test update_tool_type.
*/
public function test_mod_lti_update_tool_type(): void {
$this->setAdminUser();
$type = mod_lti_external::create_tool_type($this->getExternalTestFileUrl('/ims_cartridge_basic_lti_link.xml'), '', '');
$type = external_api::clean_returnvalue(mod_lti_external::create_tool_type_returns(), $type);
$type = mod_lti_external::update_tool_type($type['id'], 'New name', 'New description', LTI_TOOL_STATE_PENDING);
$type = external_api::clean_returnvalue(mod_lti_external::update_tool_type_returns(), $type);
$this->assertEquals('New name', $type['name']);
$this->assertEquals('New description', $type['description']);
$this->assertEquals('Pending', $type['state']['text']);
}
/**
* Test delete_tool_type for a user with the required capability.
*/
public function test_mod_lti_delete_tool_type(): void {
$this->setAdminUser();
$type = mod_lti_external::create_tool_type($this->getExternalTestFileUrl('/ims_cartridge_basic_lti_link.xml'), '', '');
$type = external_api::clean_returnvalue(mod_lti_external::create_tool_type_returns(), $type);
$this->assertNotEmpty(lti_get_type($type['id']));
$type = mod_lti_external::delete_tool_type($type['id']);
$type = external_api::clean_returnvalue(mod_lti_external::delete_tool_type_returns(), $type);
$this->assertEmpty(lti_get_type($type['id']));
}
/**
* Test delete_tool_type for a user without the required capability.
*/
public function test_mod_lti_delete_tool_type_without_capability(): void {
$this->setAdminUser();
$type = mod_lti_external::create_tool_type($this->getExternalTestFileUrl('/ims_cartridge_basic_lti_link.xml'), '', '');
$type = external_api::clean_returnvalue(mod_lti_external::create_tool_type_returns(), $type);
$this->assertNotEmpty(lti_get_type($type['id']));
$course = $this->getDataGenerator()->create_course();
$teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
$this->setUser($teacher);
$this->expectException(\required_capability_exception::class);
mod_lti_external::delete_tool_type($type['id']);
}
/**
* Test is_cartridge.
*/
public function test_mod_lti_is_cartridge(): void {
$this->setAdminUser();
$result = mod_lti_external::is_cartridge($this->getExternalTestFileUrl('/ims_cartridge_basic_lti_link.xml'));
$result = external_api::clean_returnvalue(mod_lti_external::is_cartridge_returns(), $result);
$this->assertTrue($result['iscartridge']);
$result = mod_lti_external::is_cartridge($this->getExternalTestFileUrl('/test.html'));
$result = external_api::clean_returnvalue(mod_lti_external::is_cartridge_returns(), $result);
$this->assertFalse($result['iscartridge']);
}
}
+28
View File
@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<basic_lti_link xmlns="http://www.imsglobal.org/xsd/imsbasiclti_v1p0"
xmlns:lticm ="http://www.imsglobal.org/xsd/imslticm_v1p0"
xmlns:lticp ="http://www.imsglobal.org/xsd/imslticp_v1p0"
xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation = "http://www.imsglobal.org/xsd/imsbasiclti_v1p0 imsbasiclti_v1p0p1.xsd
http://www.imsglobal.org/xsd/imslticm_v1p0 imslticm_v1p0.xsd
http://www.imsglobal.org/xsd/imslticp_v1p0 imslticp_v1p0.xsd">
<title>Example tool</title>
<description>Example tool description</description>
<extensions platform="my.lms.com">
<lticm:property name="icon_url">http://download.moodle.org/unittest/test.jpg</lticm:property>
<lticm:property name="secure_icon_url">https://download.moodle.org/unittest/test.jpg</lticm:property>
</extensions>
<launch_url>http://www.example.com/lti/provider.php</launch_url>
<secure_launch_url>https://www.example.com/lti/provider.php</secure_launch_url>
<icon>http://download.moodle.org/unittest/test.jpg</icon>
<secure_icon>https://download.moodle.org/unittest/test.jpg</secure_icon>
<vendor>
<lticp:code>moodle.org</lticp:code>
<lticp:name>Moodle</lticp:name>
<lticp:description>Moodle Learning Management System Example Cartridge</lticp:description>
<lticp:url>http://www.moodle.org/</lticp:url>
<lticp:contact>
<lticp:email>moodle@example.com</lticp:email>
</lticp:contact>
</vendor>
</basic_lti_link>
+49
View File
@@ -0,0 +1,49 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Unit tests for mod_lti edit_form
*
* @package mod_lti
* @copyright 2023 Jackson D'Souza <jackson.dsouza@catalyst-eu.net>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since Moodle 4.3
*/
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->dirroot . '/mod/lti/edit_form.php');
/**
* Testing fixture.
*
* @package mod_lti
* @copyright 2023 Jackson D'Souza <jackson.dsouza@catalyst-eu.net>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since Moodle 4.3
*/
class test_edit_form extends \mod_lti_edit_types_form {
/**
* Expose the internal moodleform's MoodleQuickForm
*
* @return MoodleQuickForm
*/
public function get_quick_form() {
return $this->_form;
}
}
+33
View File
@@ -0,0 +1,33 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Testing fixture.
*
* @package mod_lti
* @copyright 2016 John Okely
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
?>
<html>
<head>
<title>Tool provider</title>
</head>
<body>
<p>This represents a tool provider</p>
</body>
</html>
@@ -0,0 +1,98 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
defined('MOODLE_INTERNAL') || die();
/**
* Define behat generator for mod_lti.
*
* @package mod_lti
* @category test
* @author Andrew Madden <andrewmadden@catalyst-au.net>
* @copyright 2020 Catalyst IT
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class behat_mod_lti_generator extends behat_generator_base {
/**
* Get list of entities for mod_lti behat tests.
*
* @return array[] List of entity definitions.
*/
protected function get_creatable_entities(): array {
return [
'tool proxies' => [
'singular' => 'tool proxy',
'datagenerator' => 'tool_proxies',
'required' => [],
],
'tool types' => [
'singular' => 'tool type',
'datagenerator' => 'tool_types',
'required' => ['baseurl'],
'switchids' => ['lti_coursecategories' => 'lti_coursecategories']
],
'course tools' => [
'singular' => 'course tool',
'datagenerator' => 'course_tool_types',
'required' => ['baseurl', 'course'],
'switchids' => ['course' => 'course']
],
'tool instances' => [
'singular' => 'instance',
'datagenerator' => 'instance',
'required' => ['course', 'tool'],
'switchids' => ['course' => 'course', 'tool' => 'typeid']
],
];
}
/**
* Handles the switchid ['tool' => 'typeid'] for finding a tool by name.
*
* @param string $name the name of the tool.
* @return int the id of the tool type identified by the name $name.
*/
protected function get_tool_id(string $name): int {
global $DB;
if (!$id = $DB->get_field('lti_types', 'id', ['name' => $name])) {
throw new coding_exception('The specified tool with name "' . $name . '" does not exist');
}
return (int) $id;
}
/**
* Handles the switchid ['lti_coursecategories' => 'lti_coursecategories'] for restricting a tool to certain categories.
*
* @param string $idnumbers a comma-separated string containing the course category id numbers, e.g. 'cata, catb, catc'.
* @return string a comma-separated string containing the course category ids.
* @throws coding_exception if one or more of the categories is unable to be matched by its idnumber.
*/
protected function get_lti_coursecategories_id(string $idnumbers): string {
global $DB;
$categoryids = array_map('trim', explode(',', $idnumbers));
[$insql, $inparams] = $DB->get_in_or_equal($categoryids);
$ids = $DB->get_fieldset_sql("SELECT id FROM {course_categories} WHERE idnumber $insql", $inparams);
if (!$ids || count($ids) != count($categoryids)) {
throw new coding_exception("One or more course categories unable to be matched using idnumbers: $idnumbers");
}
return implode(',', $ids);
}
}
+180
View File
@@ -0,0 +1,180 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* mod_lti data generator
*
* @package mod_lti
* @category test
* @copyright Copyright (c) 2012 Moodlerooms Inc. (http://www.moodlerooms.com)
* @author Mark Nielsen
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->dirroot . '/mod/lti/locallib.php');
/**
* LTI module data generator class
*
* @package mod_lti
* @category test
* @copyright Copyright (c) 2012 Moodlerooms Inc. (http://www.moodlerooms.com)
* @author Mark Nielsen
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class mod_lti_generator extends testing_module_generator {
public function create_instance($record = null, array $options = null) {
$record = (object) (array) $record;
if (!isset($record->toolurl)) {
$record->toolurl = '';
} else {
$toolurl = new moodle_url($record->toolurl);
$record->toolurl = $toolurl->out(false);
}
if (!isset($record->resourcekey)) {
$record->resourcekey = '12345';
}
if (!isset($record->password)) {
$record->password = 'secret';
}
if (!isset($record->grade)) {
$record->grade = 100;
}
if (!isset($record->instructorchoicesendname)) {
$record->instructorchoicesendname = 1;
}
if (!isset($record->instructorchoicesendemailaddr)) {
$record->instructorchoicesendemailaddr = 1;
}
if (!isset($record->instructorchoiceacceptgrades)) {
$record->instructorchoiceacceptgrades = 1;
}
if (!isset($record->typeid)) {
$record->typeid = 0;
}
return parent::create_instance($record, (array)$options);
}
/**
* Create a tool proxy.
*
* @param array $config
*/
public function create_tool_proxies(array $config) {
if (!isset($config['capabilityoffered'])) {
$config['capabilityoffered'] = '';
}
if (!isset($config['serviceoffered'])) {
$config['serviceoffered'] = '';
}
lti_add_tool_proxy((object) $config);
}
/**
* Split type creation data into 'type' and 'config' components, based on input array key prefixes.
*
* The $data array contains both the type data and config data that will be passed to lti_add_type(). This must be split into
* two params (type, config) based on the array key prefixes ({@see lti_add_type()} for how the two params are handled):
* - NO prefix: denotes 'type' data.
* - 'lti_' prefix: denotes 'config' data.
* - 'ltiservice_' prefix: denotes 'config' data, specifically config for service plugins.
*
* @param array $data array of type and config data containing prefixed keys.
* @return array containing separated objects for type and config data. E.g. ['type' = stdClass, 'config' => stdClass]
*/
protected function get_type_and_config_from_data(array $data): array {
// Grab any non-prefixed fields; these are the type fields. The rest is considered config.
$type = array_filter(
$data,
fn($val, $key) => !str_contains($key, 'lti_') && !str_contains($key, 'ltiservice_'),
ARRAY_FILTER_USE_BOTH
);
$config = array_diff_key($data, $type);
return ['type' => (object) $type, 'config' => (object) $config];
}
/**
* Create a tool type.
*
* @param array $data
* @return int ID of created tool
*/
public function create_tool_types(array $data): int {
if (!isset($data['baseurl'])) {
throw new coding_exception('Must specify baseurl when creating a LTI tool type.');
}
$data['baseurl'] = (new moodle_url($data['baseurl']))->out(false); // Permits relative URLs in behat features.
// Sensible defaults permitting the tool type to be used in a launch.
$data['lti_acceptgrades'] = $data['lti_acceptgrades'] ?? LTI_SETTING_ALWAYS;
$data['lti_sendname'] = $data['lti_sendname'] ?? LTI_SETTING_ALWAYS;
$data['lti_sendemailaddr'] = $data['lti_sendname'] ?? LTI_SETTING_ALWAYS;
$data['lti_launchcontainer'] = $data['lti_launchcontainer'] ?? LTI_LAUNCH_CONTAINER_EMBED_NO_BLOCKS;
['type' => $type, 'config' => $config] = $this->get_type_and_config_from_data($data);
return lti_add_type(type: $type, config: $config);
}
/**
* Create a course tool type.
*
* @param array $type the type info.
* @return int ID of created tool.
* @throws coding_exception if any required fields are missing.
*/
public function create_course_tool_types(array $type): int {
global $SITE;
if (!isset($type['baseurl'])) {
throw new coding_exception('Must specify baseurl when creating a course tool type.');
}
if (!isset($type['course']) || $type['course'] == $SITE->id) {
throw new coding_exception('Must specify a non-site course when creating a course tool type.');
}
$type['baseurl'] = (new moodle_url($type['baseurl']))->out(false); // Permits relative URLs in behat features.
$type['coursevisible'] = $type['coursevisible'] ?? LTI_COURSEVISIBLE_ACTIVITYCHOOSER;
$type['state'] = LTI_TOOL_STATE_CONFIGURED; // The default for course tools.
// Sensible defaults permitting the tool type to be used in a launch.
$type['lti_acceptgrades'] = $type['lti_acceptgrades'] ?? LTI_SETTING_ALWAYS;
$type['lti_sendname'] = $type['lti_sendname'] ?? LTI_SETTING_ALWAYS;
$type['lti_sendemailaddr'] = $type['lti_sendemailaddr'] ?? LTI_SETTING_ALWAYS;
$type['lti_coursevisible'] = $type['coursevisible'] ?? LTI_COURSEVISIBLE_ACTIVITYCHOOSER;
$type['lti_launchcontainer'] = $type['lti_launchcontainer'] ?? LTI_LAUNCH_CONTAINER_EMBED_NO_BLOCKS;
// Required for cartridge processing support.
$type['lti_toolurl'] = $type['baseurl'];
$type['lti_description'] = $type['description'] ?? '';
$type['lti_icon'] = $type['icon'] ?? '';
$type['lti_secureicon'] = $type['secureicon'] ?? '';
if (!empty($type['name'])) {
$type['lti_typename'] = $type['name'];
}
['type' => $type, 'config' => $config] = $this->get_type_and_config_from_data($type);
lti_load_type_if_cartridge($config);
return lti_add_type(type: $type, config: $config);
}
}
+67
View File
@@ -0,0 +1,67 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace mod_lti;
/**
* PHPUnit data generator testcase
*
* @package mod_lti
* @category phpunit
* @copyright Copyright (c) 2012 Moodlerooms Inc. (http://www.moodlerooms.com)
* @author Mark Nielsen
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class generator_test extends \advanced_testcase {
public function test_generator(): void {
global $DB;
$this->resetAfterTest(true);
$this->assertEquals(0, $DB->count_records('lti'));
$course = $this->getDataGenerator()->create_course();
/*
* @var mod_lti_generator $generator
*/
$generator = $this->getDataGenerator()->get_plugin_generator('mod_lti');
$this->assertInstanceOf('mod_lti_generator', $generator);
$this->assertEquals('lti', $generator->get_modulename());
$generator->create_instance(array('course' => $course->id));
$generator->create_instance(array('course' => $course->id));
$lti = $generator->create_instance(array('course' => $course->id));
$this->assertEquals(3, $DB->count_records('lti'));
$cm = get_coursemodule_from_instance('lti', $lti->id);
$this->assertEquals($lti->id, $cm->instance);
$this->assertEquals('lti', $cm->modname);
$this->assertEquals($course->id, $cm->course);
$context = \context_module::instance($cm->id);
$this->assertEquals($lti->cmid, $context->instanceid);
// Test gradebook integration using low level DB access - DO NOT USE IN PLUGIN CODE!
$lti = $generator->create_instance(array('course' => $course->id, 'assessed' => 1, 'scale' => 100));
$gitem = $DB->get_record('grade_items', array('courseid' => $course->id, 'itemtype' => 'mod',
'itemmodule' => 'lti', 'iteminstance' => $lti->id));
$this->assertNotEmpty($gitem);
$this->assertEquals(100, $gitem->grademax);
$this->assertEquals(0, $gitem->grademin);
$this->assertEquals(GRADE_TYPE_VALUE, $gitem->gradetype);
}
}
+458
View File
@@ -0,0 +1,458 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Unit tests for mod_lti lib
*
* @package mod_lti
* @category external
* @copyright 2015 Juan Leyva <juan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since Moodle 3.0
*/
namespace mod_lti;
defined('MOODLE_INTERNAL') || die();
/**
* Unit tests for mod_lti lib
*
* @package mod_lti
* @category external
* @copyright 2015 Juan Leyva <juan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since Moodle 3.0
*/
class lib_test extends \advanced_testcase {
/**
* Prepares things before this test case is initialised
* @return void
*/
public static function setUpBeforeClass(): void {
global $CFG;
require_once($CFG->dirroot . '/mod/lti/lib.php');
}
/**
* Test lti_view
* @return void
*/
public function test_lti_view(): void {
global $CFG;
$CFG->enablecompletion = 1;
$this->resetAfterTest();
$this->setAdminUser();
// Setup test data.
$course = $this->getDataGenerator()->create_course(array('enablecompletion' => 1));
$lti = $this->getDataGenerator()->create_module('lti', array('course' => $course->id),
array('completion' => 2, 'completionview' => 1));
$context = \context_module::instance($lti->cmid);
$cm = get_coursemodule_from_instance('lti', $lti->id);
// Trigger and capture the event.
$sink = $this->redirectEvents();
lti_view($lti, $course, $cm, $context);
$events = $sink->get_events();
// 2 additional events thanks to completion.
$this->assertCount(3, $events);
$event = array_shift($events);
// Checking that the event contains the expected values.
$this->assertInstanceOf('\mod_lti\event\course_module_viewed', $event);
$this->assertEquals($context, $event->get_context());
$moodleurl = new \moodle_url('/mod/lti/view.php', array('id' => $cm->id));
$this->assertEquals($moodleurl, $event->get_url());
$this->assertEventContextNotUsed($event);
$this->assertNotEmpty($event->get_name());
// Check completion status.
$completion = new \completion_info($course);
$completiondata = $completion->get_data($cm);
$this->assertEquals(1, $completiondata->completionstate);
}
/**
* Test deleting LTI instance.
*/
public function test_lti_delete_instance(): void {
$this->resetAfterTest();
$this->setAdminUser();
$course = $this->getDataGenerator()->create_course(array());
$lti = $this->getDataGenerator()->create_module('lti', array('course' => $course->id));
$cm = get_coursemodule_from_instance('lti', $lti->id);
// Must not throw notices.
course_delete_module($cm->id);
}
public function test_lti_core_calendar_provide_event_action(): void {
$this->resetAfterTest();
$this->setAdminUser();
// Create the activity.
$course = $this->getDataGenerator()->create_course();
$lti = $this->getDataGenerator()->create_module('lti', array('course' => $course->id));
// Create a calendar event.
$event = $this->create_action_event($course->id, $lti->id,
\core_completion\api::COMPLETION_EVENT_TYPE_DATE_COMPLETION_EXPECTED);
// Create an action factory.
$factory = new \core_calendar\action_factory();
// Decorate action event.
$actionevent = mod_lti_core_calendar_provide_event_action($event, $factory);
// Confirm the event was decorated.
$this->assertInstanceOf('\core_calendar\local\event\value_objects\action', $actionevent);
$this->assertEquals(get_string('view'), $actionevent->get_name());
$this->assertInstanceOf('moodle_url', $actionevent->get_url());
$this->assertEquals(1, $actionevent->get_item_count());
$this->assertTrue($actionevent->is_actionable());
}
public function test_lti_core_calendar_provide_event_action_as_non_user(): void {
global $CFG;
$this->resetAfterTest();
$this->setAdminUser();
// Create the activity.
$course = $this->getDataGenerator()->create_course();
$lti = $this->getDataGenerator()->create_module('lti', array('course' => $course->id));
// Create a calendar event.
$event = $this->create_action_event($course->id, $lti->id,
\core_completion\api::COMPLETION_EVENT_TYPE_DATE_COMPLETION_EXPECTED);
// Now, log out.
$CFG->forcelogin = true; // We don't want to be logged in as guest, as guest users might still have some capabilities.
$this->setUser();
// Create an action factory.
$factory = new \core_calendar\action_factory();
// Decorate action event.
$actionevent = mod_lti_core_calendar_provide_event_action($event, $factory);
// Confirm the event is not shown at all.
$this->assertNull($actionevent);
}
public function test_lti_core_calendar_provide_event_action_for_user(): void {
global $CFG;
$this->resetAfterTest();
$this->setAdminUser();
// Create the activity.
$course = $this->getDataGenerator()->create_course();
$lti = $this->getDataGenerator()->create_module('lti', array('course' => $course->id));
// Enrol a student in the course.
$student = $this->getDataGenerator()->create_and_enrol($course, 'student');
// Create a calendar event.
$event = $this->create_action_event($course->id, $lti->id,
\core_completion\api::COMPLETION_EVENT_TYPE_DATE_COMPLETION_EXPECTED);
// Now, log out.
$CFG->forcelogin = true; // We don't want to be logged in as guest, as guest users might still have some capabilities.
$this->setUser();
// Create an action factory.
$factory = new \core_calendar\action_factory();
// Decorate action event for the student.
$actionevent = mod_lti_core_calendar_provide_event_action($event, $factory, $student->id);
// Confirm the event was decorated.
$this->assertInstanceOf('\core_calendar\local\event\value_objects\action', $actionevent);
$this->assertEquals(get_string('view'), $actionevent->get_name());
$this->assertInstanceOf('moodle_url', $actionevent->get_url());
$this->assertEquals(1, $actionevent->get_item_count());
$this->assertTrue($actionevent->is_actionable());
}
public function test_lti_core_calendar_provide_event_action_already_completed(): void {
global $CFG;
$this->resetAfterTest();
$this->setAdminUser();
$CFG->enablecompletion = 1;
// Create the activity.
$course = $this->getDataGenerator()->create_course(array('enablecompletion' => 1));
$lti = $this->getDataGenerator()->create_module('lti', array('course' => $course->id),
array('completion' => 2, 'completionview' => 1, 'completionexpected' => time() + DAYSECS));
// Get some additional data.
$cm = get_coursemodule_from_instance('lti', $lti->id);
// Create a calendar event.
$event = $this->create_action_event($course->id, $lti->id,
\core_completion\api::COMPLETION_EVENT_TYPE_DATE_COMPLETION_EXPECTED);
// Mark the activity as completed.
$completion = new \completion_info($course);
$completion->set_module_viewed($cm);
// Create an action factory.
$factory = new \core_calendar\action_factory();
// Decorate action event.
$actionevent = mod_lti_core_calendar_provide_event_action($event, $factory);
// Ensure result was null.
$this->assertNull($actionevent);
}
public function test_lti_core_calendar_provide_event_action_already_completed_as_non_user(): void {
global $CFG;
$this->resetAfterTest();
$this->setAdminUser();
$CFG->enablecompletion = 1;
// Create the activity.
$course = $this->getDataGenerator()->create_course(array('enablecompletion' => 1));
$lti = $this->getDataGenerator()->create_module('lti', array('course' => $course->id),
array('completion' => 2, 'completionview' => 1, 'completionexpected' => time() + DAYSECS));
// Get some additional data.
$cm = get_coursemodule_from_instance('lti', $lti->id);
// Create a calendar event.
$event = $this->create_action_event($course->id, $lti->id,
\core_completion\api::COMPLETION_EVENT_TYPE_DATE_COMPLETION_EXPECTED);
// Mark the activity as completed.
$completion = new \completion_info($course);
$completion->set_module_viewed($cm);
// Now, log out.
$CFG->forcelogin = true; // We don't want to be logged in as guest, as guest users might still have some capabilities.
$this->setUser();
// Create an action factory.
$factory = new \core_calendar\action_factory();
// Decorate action event.
$actionevent = mod_lti_core_calendar_provide_event_action($event, $factory);
// Ensure result was null.
$this->assertNull($actionevent);
}
public function test_lti_core_calendar_provide_event_action_already_completed_for_user(): void {
global $CFG;
$this->resetAfterTest();
$this->setAdminUser();
$CFG->enablecompletion = 1;
// Create the activity.
$course = $this->getDataGenerator()->create_course(array('enablecompletion' => 1));
$lti = $this->getDataGenerator()->create_module('lti', array('course' => $course->id),
array('completion' => 2, 'completionview' => 1, 'completionexpected' => time() + DAYSECS));
// Enrol 2 students in the course.
$student1 = $this->getDataGenerator()->create_and_enrol($course, 'student');
$student2 = $this->getDataGenerator()->create_and_enrol($course, 'student');
// Get some additional data.
$cm = get_coursemodule_from_instance('lti', $lti->id);
// Create a calendar event.
$event = $this->create_action_event($course->id, $lti->id,
\core_completion\api::COMPLETION_EVENT_TYPE_DATE_COMPLETION_EXPECTED);
// Mark the activity as completed for $student1.
$completion = new \completion_info($course);
$completion->set_module_viewed($cm, $student1->id);
// Now, log in as $student2.
$this->setUser($student2);
// Create an action factory.
$factory = new \core_calendar\action_factory();
// Decorate action event for $student1.
$actionevent = mod_lti_core_calendar_provide_event_action($event, $factory, $student1->id);
// Ensure result was null.
$this->assertNull($actionevent);
}
/**
* Creates an action event.
*
* @param int $courseid The course id.
* @param int $instanceid The instance id.
* @param string $eventtype The event type.
* @return bool|calendar_event
*/
private function create_action_event($courseid, $instanceid, $eventtype) {
$event = new \stdClass();
$event->name = 'Calendar event';
$event->modulename = 'lti';
$event->courseid = $courseid;
$event->instance = $instanceid;
$event->type = CALENDAR_EVENT_TYPE_ACTION;
$event->eventtype = $eventtype;
$event->timestart = time();
return \calendar_event::create($event);
}
/**
* Test verifying the output of the lti_get_course_content_items and lti_get_all_content_items callbacks.
*/
public function test_content_item_callbacks(): void {
$this->resetAfterTest();
global $DB, $CFG;
require_once($CFG->dirroot . '/mod/lti/locallib.php');
$admin = get_admin();
$time = time();
$course = $this->getDataGenerator()->create_course();
$teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
$course2 = $this->getDataGenerator()->create_course();
$teacher2 = $this->getDataGenerator()->create_and_enrol($course2, 'editingteacher');
// Create some preconfigured tools.
$sitetoolrecord = (object) [
'name' => 'Site level tool which is available in the activity chooser',
'baseurl' => 'http://example.com',
'createdby' => $admin->id,
'course' => SITEID,
'ltiversion' => 'LTI-1p0',
'timecreated' => $time,
'timemodified' => $time,
'state' => LTI_TOOL_STATE_CONFIGURED,
'coursevisible' => LTI_COURSEVISIBLE_ACTIVITYCHOOSER
];
$sitetoolrecordnonchooser = (object) [
'name' => 'Site level tool which is NOT available in the course activity chooser',
'baseurl' => 'http://example2.com',
'createdby' => $admin->id,
'course' => SITEID,
'ltiversion' => 'LTI-1p0',
'timecreated' => $time,
'timemodified' => $time,
'state' => LTI_TOOL_STATE_CONFIGURED,
'coursevisible' => LTI_COURSEVISIBLE_PRECONFIGURED
];
$course1toolrecord = (object) [
'name' => 'Course created tool which is available in the activity chooser',
'baseurl' => 'http://example3.com',
'createdby' => $teacher->id,
'course' => $course->id,
'ltiversion' => 'LTI-1p0',
'timecreated' => $time,
'timemodified' => $time,
'state' => LTI_TOOL_STATE_CONFIGURED,
'coursevisible' => LTI_COURSEVISIBLE_ACTIVITYCHOOSER
];
$course2toolrecord = (object) [
'name' => 'Course created tool which is available in the activity chooser',
'baseurl' => 'http://example4.com',
'createdby' => $teacher2->id,
'course' => $course2->id,
'ltiversion' => 'LTI-1p0',
'timecreated' => $time,
'timemodified' => $time,
'state' => LTI_TOOL_STATE_CONFIGURED,
'coursevisible' => LTI_COURSEVISIBLE_ACTIVITYCHOOSER
];
$tool1id = $DB->insert_record('lti_types', $sitetoolrecord);
$tool2id = $DB->insert_record('lti_types', $sitetoolrecordnonchooser);
$tool3id = $DB->insert_record('lti_types', $course1toolrecord);
$tool4id = $DB->insert_record('lti_types', $course2toolrecord);
$sitetoolrecord->id = $tool1id;
$sitetoolrecordnonchooser->id = $tool2id;
$course1toolrecord->id = $tool3id;
$course2toolrecord->id = $tool4id;
$defaultmodulecontentitem = new \core_course\local\entity\content_item(
'1',
'default module content item',
new \core_course\local\entity\string_title('Content item title'),
new \moodle_url(''),
'icon',
'Description of the module',
MOD_ARCHETYPE_OTHER,
'mod_lti',
MOD_PURPOSE_CONTENT
);
// The lti_get_lti_types_by_course method (used by the callbacks) assumes the global user.
$this->setUser($teacher);
// Teacher in course1 should be able to see the site preconfigured tool and the tool created in course1.
$courseitems = lti_get_course_content_items($defaultmodulecontentitem, $teacher, $course);
$this->assertCount(2, $courseitems);
$ids = [];
foreach ($courseitems as $item) {
$ids[] = $item->get_id();
}
$this->assertContains($sitetoolrecord->id + 1, $ids);
$this->assertContains($course1toolrecord->id + 1, $ids);
$this->assertNotContains($sitetoolrecordnonchooser->id + 1, $ids);
// The content items for teacher2 in course2 include the site preconfigured tool and the tool created in course2.
$this->setUser($teacher2);
$course2items = lti_get_course_content_items($defaultmodulecontentitem, $teacher2, $course2);
$this->assertCount(2, $course2items);
$ids = [];
foreach ($course2items as $item) {
$ids[] = $item->get_id();
}
$this->assertContains($sitetoolrecord->id + 1, $ids);
$this->assertContains($course2toolrecord->id + 1, $ids);
$this->assertNotContains($sitetoolrecordnonchooser->id + 1, $ids);
// Removing the capability to use preconfigured (site or course level) tools, should result in no content items.
$teacherrole = $DB->get_record('role', array('shortname' => 'editingteacher'));
assign_capability('mod/lti:addpreconfiguredinstance', CAP_PROHIBIT, $teacherrole->id,
\core\context\course::instance($course2->id));
$course2items = lti_get_course_content_items($defaultmodulecontentitem, $teacher2, $course2);
$this->assertCount(0, $course2items);
// When fetching all content items, we expect to see all items available in activity choosers (in any course).
$this->setAdminUser();
$allitems = mod_lti_get_all_content_items($defaultmodulecontentitem);
$this->assertCount(3, $allitems);
$ids = [];
foreach ($allitems as $item) {
$ids[] = $item->get_id();
}
$this->assertContains($sitetoolrecord->id + 1, $ids);
$this->assertContains($course1toolrecord->id + 1, $ids);
$this->assertContains($course2toolrecord->id + 1, $ids);
$this->assertNotContains($sitetoolrecordnonchooser->id + 1, $ids);
}
}
@@ -0,0 +1,260 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
//
// This file is part of BasicLTI4Moodle
//
// BasicLTI4Moodle is an IMS BasicLTI (Basic Learning Tools for Interoperability)
// consumer for Moodle 1.9 and Moodle 2.0. BasicLTI is a IMS Standard that allows web
// based learning tools to be easily integrated in LMS as native ones. The IMS BasicLTI
// specification is part of the IMS standard Common Cartridge 1.1 Sakai and other main LMS
// are already supporting or going to support BasicLTI. This project Implements the consumer
// for Moodle. Moodle is a Free Open source Learning Management System by Martin Dougiamas.
// BasicLTI4Moodle is a project iniciated and leaded by Ludo(Marc Alier) and Jordi Piguillem
// at the GESSI research group at UPC.
// SimpleLTI consumer for Moodle is an implementation of the early specification of LTI
// by Charles Severance (Dr Chuck) htp://dr-chuck.com , developed by Jordi Piguillem in a
// Google Summer of Code 2008 project co-mentored by Charles Severance and Marc Alier.
//
// BasicLTI4Moodle is copyright 2009 by Marc Alier Forment, Jordi Piguillem and Nikolas Galanis
// of the Universitat Politecnica de Catalunya http://www.upc.edu
// Contact info: Marc Alier Forment granludo @ gmail.com or marc.alier @ upc.edu.
namespace mod_lti\local\ltiopenid;
/**
* Tests for the jwks_helper class.
*
* @coversDefaultClass \mod_lti\local\ltiopenid\jwks_helper
* @package mod_lti
* @copyright 2023 Jake Dallimore <jrhdallimore@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class jwks_helper_test extends \basic_testcase {
/**
* Test the fix_jwks_alg method with a range of inputs.
*
* @dataProvider jwks_alg_provider
* @covers ::fix_jwks_alg
* @param array $jwks the JWKS key set.
* @param string $jwt the JWT.
* @param array $expected the expected outputs/exceptions.
* @return void
*/
public function test_fix_jwks_alg(array $jwks, string $jwt, array $expected): void {
if (isset($expected['exception'])) {
$this->expectException($expected['exception']);
}
$fixed = jwks_helper::fix_jwks_alg($jwks, $jwt);
$this->assertEquals($expected['jwks'], $fixed);
}
/**
* Provider for test_fix_jwks_alg.
* @return array test data.
*/
public function jwks_alg_provider(): array {
return [
// Algs already present, so no changes to input key array.
'All JWKS keys have algs set' => [
'jwks' => [
'keys' => [
[
'kty' => 'RSA',
'use' => 'sig',
'e' => 'AQAB',
'n' => '3nVf6',
'kid' => '41',
'alg' => 'RS256'
],
[
'kty' => 'RSA',
'use' => 'sig',
'e' => 'AQAB',
'n' => '3nVf6',
'kid' => '42',
'alg' => 'RS256'
]
]
],
// RS256 JWT with kid = 42.
'jwt' => 'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjQyIn0.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IlRoZSBjYXQiLCJz'.
'bG9nYW4iOiJMb3ZlcyBpdCwgbG92ZXMgaXQsIGxvdmVzIE1vb2RsZSIsImlhdCI6MTUxNjIzOTAyMn0.EiqMEqufKJj74JevdTxXqzHvHGIcZ'.
'EFYhOe9sliL2FmlyiJcf7waObO2ZNwWvVZwTI4DfEGFamheMmTb6-YBODacDvH6BlQNb0H_6ye6AGl1u-3OAQj7i_SKsLuB37k6Lw5YFrwQYr'.
'7bjujSaQx6BL3kaqkqCdZhFjr2EYcn5-NehGHsevKqpMA-ShBovcndYkD5gfZEbXr59sgpQuJ43qO7gnGPzRbaJAEw_0_6v0r3y0pzDNfarNd'.
'fHfCZQbcF9T8dpHAeO4JMmuCanV8iJziI8ihVPwH-BwUJmzthyUgy8542FinHVbXo-88wu9xpbdV17VPgeGGBCpYpnVnWaA',
'expected' => [
'jwks' => [
'keys' => [
[
'kty' => 'RSA',
'use' => 'sig',
'e' => 'AQAB',
'n' => '3nVf6',
'kid' => '41',
'alg' => 'RS256'
],
[
'kty' => 'RSA',
'use' => 'sig',
'e' => 'AQAB',
'n' => '3nVf6',
'kid' => '42',
'alg' => 'RS256'
]
]
]
]
],
// Only the key matching the kid in the JWT header should be fixed.
'All JWKS keys missing alg' => [
'jwks' => [
'keys' => [
[
'kty' => 'RSA',
'use' => 'sig',
'e' => 'AQAB',
'n' => '3nVf6',
'kid' => '41',
],
[
'kty' => 'RSA',
'use' => 'sig',
'e' => 'AQAB',
'n' => '3nVf6',
'kid' => '42',
]
]
],
// RS256 JWT with kid = 42.
'jwt' => 'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjQyIn0.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IlRoZSBjYXQiLCJz'.
'bG9nYW4iOiJMb3ZlcyBpdCwgbG92ZXMgaXQsIGxvdmVzIE1vb2RsZSIsImlhdCI6MTUxNjIzOTAyMn0.EiqMEqufKJj74JevdTxXqzHvHGIcZ'.
'EFYhOe9sliL2FmlyiJcf7waObO2ZNwWvVZwTI4DfEGFamheMmTb6-YBODacDvH6BlQNb0H_6ye6AGl1u-3OAQj7i_SKsLuB37k6Lw5YFrwQYr'.
'7bjujSaQx6BL3kaqkqCdZhFjr2EYcn5-NehGHsevKqpMA-ShBovcndYkD5gfZEbXr59sgpQuJ43qO7gnGPzRbaJAEw_0_6v0r3y0pzDNfarNd'.
'fHfCZQbcF9T8dpHAeO4JMmuCanV8iJziI8ihVPwH-BwUJmzthyUgy8542FinHVbXo-88wu9xpbdV17VPgeGGBCpYpnVnWaA',
'expected' => [
'jwks' => [
'keys' => [
[
'kty' => 'RSA',
'use' => 'sig',
'e' => 'AQAB',
'n' => '3nVf6',
'kid' => '41',
],
[
'kty' => 'RSA',
'use' => 'sig',
'e' => 'AQAB',
'n' => '3nVf6',
'kid' => '42',
'alg' => 'RS256'
]
]
]
]
],
// Exception expected when JWT alg is supported but does not match the family of key in the JWK.
'JWT kty algorithm family mismatch' => [
'jwks' => [
'keys' => [
[
'kty' => 'RSA',
'use' => 'sig',
'e' => 'AQAB',
'n' => '3nVf6',
'kid' => '41',
],
[
'kty' => 'RSA',
'use' => 'sig',
'e' => 'AQAB',
'n' => '3nVf6',
'kid' => '42',
]
]
],
// ES256 JWT with kid = 42.
'jwt' => 'eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjQyIn0.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwi'.
'YWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0.dbUlZopFo7164JVLD0G4GoZOhoMYWhIXkgtlblBXT6fC3K4lJ38l3LzlEBhfRRKvJlXpe'.
'NNGmBg8V29jd5J33Q',
'expected' => [
'exception' => \moodle_exception::class
]
],
// Exception expected when JWK kid field missing.
'JWT missing kid header field' => [
'jwks' => [
'keys' => [
[
'kty' => 'RSA',
'use' => 'sig',
'e' => 'AQAB',
'n' => '3nVf6',
'kid' => '41',
'alg' => 'RS256'
],
[
'kty' => 'RSA',
'use' => 'sig',
'e' => 'AQAB',
'n' => '3nVf6',
'kid' => '42',
'alg' => 'RS256'
]
]
],
// RS256 JWT with kid omitted.
'jwt' => 'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWU'.
'sImlhdCI6MTUxNjIzOTAyMn0.NHVaYe26MbtOYhSKkoKYdFVomg4i8ZJd8_-RU8VNbftc4TSMb4bXP3l3YlNWACwyXPGffz5aXHc6lty1Y2t4'.
'SWRqGteragsVdZufDn5BlnJl9pdR_kdVFUsra2rWKEofkZeIC4yWytE58sMIihvo9H1ScmmVwBcQP6XETqYd0aSHp1gOa9RdUPDvoXQ5oqygT'.
'qVtxaDr6wUFKrKItgBMzWIdNZ6y7O9E0DhEPTbE9rfBo6KTFsHAZnMg4k68CDp2woYIaXbmYTWcvbzIuHO7_37GT79XdIwkm95QJ7hYC9Riwr'.
'V7mesbY4PAahERJawntho0my942XheVLmGwLMBkQ',
'expected' => [
'exception' => \moodle_exception::class
]
],
// Exception expected when JWT passes unsupported symmetrical alg.
'JWT passes in unsupported alg' => [
'jwks' => [
'keys' => [
[
'kty' => 'RSA',
'use' => 'sig',
'e' => 'AQAB',
'n' => '3nVf6',
'kid' => '41',
],
[
'kty' => 'RSA',
'use' => 'sig',
'e' => 'AQAB',
'n' => '3nVf6',
'kid' => '42',
]
]
],
// HS256 JWT with kid = 42.
'jwt' => 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjQyIn0.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IlRoZSBjYXQiLCJz'.
'bG9nYW4iOiJMb3ZlcyBpdCwgbG92ZXMgaXQsIGxvdmVzIE1vb2RsZSIsImlhdCI6MTUxNjIzOTAyMn0.zBM5Jw0BOig5-C1R7TD-TzH1QVmyD'.
'yMjbK0KGG76xIE',
'expected' => [
'exception' => \moodle_exception::class
]
],
];
}
}
@@ -0,0 +1,435 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
//
// This file is part of BasicLTI4Moodle
//
// BasicLTI4Moodle is an IMS BasicLTI (Basic Learning Tools for Interoperability)
// consumer for Moodle 1.9 and Moodle 2.0. BasicLTI is a IMS Standard that allows web
// based learning tools to be easily integrated in LMS as native ones. The IMS BasicLTI
// specification is part of the IMS standard Common Cartridge 1.1 Sakai and other main LMS
// are already supporting or going to support BasicLTI. This project Implements the consumer
// for Moodle. Moodle is a Free Open source Learning Management System by Martin Dougiamas.
// BasicLTI4Moodle is a project iniciated and leaded by Ludo(Marc Alier) and Jordi Piguillem
// at the GESSI research group at UPC.
// SimpleLTI consumer for Moodle is an implementation of the early specification of LTI
// by Charles Severance (Dr Chuck) htp://dr-chuck.com , developed by Jordi Piguillem in a
// Google Summer of Code 2008 project co-mentored by Charles Severance and Marc Alier.
//
// BasicLTI4Moodle is copyright 2009 by Marc Alier Forment, Jordi Piguillem and Nikolas Galanis
// of the Universitat Politecnica de Catalunya http://www.upc.edu
// Contact info: Marc Alier Forment granludo @ gmail.com or marc.alier @ upc.edu.
namespace mod_lti\local\ltiopenid;
/**
* OpenId LTI Registration library tests
*
* @package mod_lti
* @copyright 2020 Claude Vervoort, Cengage
* @author Claude Vervoort
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class registration_test extends \advanced_testcase {
/**
* @var string A has-it-all client registration.
*/
private $registrationfulljson = <<<EOD
{
"application_type": "web",
"response_types": ["id_token"],
"grant_types": ["implict", "client_credentials"],
"initiate_login_uri": "https://client.example.org/lti/init",
"redirect_uris":
["https://client.example.org/callback",
"https://client.example.org/callback2"],
"client_name": "Virtual Garden",
"client_name#ja": "バーチャルガーデン",
"jwks_uri": "https://client.example.org/.well-known/jwks.json",
"logo_uri": "https://client.example.org/logo.png",
"policy_uri": "https://client.example.org/privacy",
"policy_uri#ja": "https://client.example.org/privacy?lang=ja",
"tos_uri": "https://client.example.org/tos",
"tos_uri#ja": "https://client.example.org/tos?lang=ja",
"token_endpoint_auth_method": "private_key_jwt",
"contacts": ["ve7jtb@example.org", "mary@example.org"],
"scope": "https://purl.imsglobal.org/spec/lti-ags/scope/score https://purl.imsglobal.org/spec/lti-ags/scope/lineitem",
"https://purl.imsglobal.org/spec/lti-tool-configuration": {
"domain": "client.example.org",
"description": "Learn Botany by tending to your little (virtual) garden.",
"description#ja": "小さな(仮想)庭に行くことで植物学を学びましょう。",
"target_link_uri": "https://client.example.org/lti",
"custom_parameters": {
"context_history": "\$Context.id.history"
},
"claims": ["iss", "sub", "name", "given_name", "family_name", "email"],
"messages": [
{
"type": "LtiDeepLinkingRequest",
"target_link_uri": "https://client.example.org/lti/dl",
"label": "Add a virtual garden",
"label#ja": "バーチャルガーデンを追加する"
}
]
}
}
EOD;
/**
* @var string A minimalist client registration.
*/
private $registrationminimaljson = <<<EOD
{
"application_type": "web",
"response_types": ["id_token"],
"grant_types": ["implict", "client_credentials"],
"initiate_login_uri": "https://client.example.org/lti/init",
"redirect_uris":
["https://client.example.org/callback"],
"client_name": "Virtual Garden",
"jwks_uri": "https://client.example.org/.well-known/jwks.json",
"token_endpoint_auth_method": "private_key_jwt",
"https://purl.imsglobal.org/spec/lti-tool-configuration": {
"domain": "www.example.org",
"target_link_uri": "https://www.example.org/lti"
}
}
EOD;
/**
* @var string A minimalist with deep linking client registration.
*/
private $registrationminimaldljson = <<<EOD
{
"application_type": "web",
"response_types": ["id_token"],
"grant_types": ["implict", "client_credentials"],
"initiate_login_uri": "https://client.example.org/lti/init",
"redirect_uris":
["https://client.example.org/callback"],
"client_name": "Virtual Garden",
"jwks_uri": "https://client.example.org/.well-known/jwks.json",
"token_endpoint_auth_method": "private_key_jwt",
"https://purl.imsglobal.org/spec/lti-tool-configuration": {
"domain": "client.example.org",
"target_link_uri": "https://client.example.org/lti",
"messages": [
{
"type": "LtiDeepLinkingRequest"
}
]
}
}
EOD;
/**
* Test the mapping from Registration JSON to LTI Config for a has-it-all tool registration.
*/
public function test_to_config_full(): void {
$registration = json_decode($this->registrationfulljson, true);
$registration['scope'] .= ' https://purl.imsglobal.org/spec/lti-nrps/scope/contextmembership.readonly';
$config = registration_helper::get()->registration_to_config($registration, 'TheClientId');
$this->assertEquals('JWK_KEYSET', $config->lti_keytype);
$this->assertEquals(LTI_VERSION_1P3, $config->lti_ltiversion);
$this->assertEquals('TheClientId', $config->lti_clientid);
$this->assertEquals('Virtual Garden', $config->lti_typename);
$this->assertEquals('Learn Botany by tending to your little (virtual) garden.', $config->lti_description);
$this->assertEquals('https://client.example.org/lti/init', $config->lti_initiatelogin);
$this->assertEquals(implode(PHP_EOL, ["https://client.example.org/callback",
"https://client.example.org/callback2"]), $config->lti_redirectionuris);
$this->assertEquals("context_history=\$Context.id.history", $config->lti_customparameters);
$this->assertEquals("https://client.example.org/.well-known/jwks.json", $config->lti_publickeyset);
$this->assertEquals("https://client.example.org/logo.png", $config->lti_icon);
$this->assertEquals(2, $config->ltiservice_gradesynchronization);
$this->assertEquals(LTI_SETTING_DELEGATE, $config->lti_acceptgrades);
$this->assertEquals(1, $config->ltiservice_memberships);
$this->assertEquals(0, $config->ltiservice_toolsettings);
$this->assertEquals('client.example.org', $config->lti_tooldomain);
$this->assertEquals('https://client.example.org/lti', $config->lti_toolurl);
$this->assertEquals(LTI_SETTING_ALWAYS, $config->lti_sendname);
$this->assertEquals(LTI_SETTING_ALWAYS, $config->lti_sendemailaddr);
$this->assertEquals(1, $config->lti_contentitem);
$this->assertEquals('https://client.example.org/lti/dl', $config->lti_toolurl_ContentItemSelectionRequest);
}
/**
* Test the mapping from Registration JSON to LTI Config for a minimal tool registration.
*/
public function test_to_config_minimal(): void {
$registration = json_decode($this->registrationminimaljson, true);
$config = registration_helper::get()->registration_to_config($registration, 'TheClientId');
$this->assertEquals('JWK_KEYSET', $config->lti_keytype);
$this->assertEquals(LTI_VERSION_1P3, $config->lti_ltiversion);
$this->assertEquals('TheClientId', $config->lti_clientid);
$this->assertEquals('Virtual Garden', $config->lti_typename);
$this->assertEmpty($config->lti_description);
// Special case here where Moodle ignores www for domains.
$this->assertEquals('example.org', $config->lti_tooldomain);
$this->assertEquals('https://www.example.org/lti', $config->lti_toolurl);
$this->assertEquals('https://client.example.org/lti/init', $config->lti_initiatelogin);
$this->assertEquals('https://client.example.org/callback', $config->lti_redirectionuris);
$this->assertEmpty($config->lti_customparameters);
$this->assertEquals("https://client.example.org/.well-known/jwks.json", $config->lti_publickeyset);
$this->assertEmpty($config->lti_icon);
$this->assertEquals(0, $config->ltiservice_gradesynchronization);
$this->assertEquals(LTI_SETTING_NEVER, $config->lti_acceptgrades);
$this->assertEquals(0, $config->ltiservice_memberships);
$this->assertEquals(LTI_SETTING_NEVER, $config->lti_sendname);
$this->assertEquals(LTI_SETTING_NEVER, $config->lti_sendemailaddr);
$this->assertEquals(0, $config->lti_contentitem);
}
/**
* Test the mapping from Registration JSON to LTI Config for a minimal tool with
* deep linking support registration.
*/
public function test_to_config_minimal_with_deeplinking(): void {
$registration = json_decode($this->registrationminimaldljson, true);
$config = registration_helper::get()->registration_to_config($registration, 'TheClientId');
$this->assertEquals(1, $config->lti_contentitem);
$this->assertEmpty($config->lti_toolurl_ContentItemSelectionRequest);
}
/**
* Validation Test: initiation login.
*/
public function test_validation_initlogin(): void {
$registration = json_decode($this->registrationfulljson, true);
$this->expectException(registration_exception::class);
$this->expectExceptionCode(400);
unset($registration['initiate_login_uri']);
registration_helper::get()->registration_to_config($registration, 'TheClientId');
}
/**
* Validation Test: redirect uris.
*/
public function test_validation_redirecturis(): void {
$registration = json_decode($this->registrationfulljson, true);
$this->expectException(registration_exception::class);
$this->expectExceptionCode(400);
unset($registration['redirect_uris']);
registration_helper::get()->registration_to_config($registration, 'TheClientId');
}
/**
* Validation Test: jwks uri empty.
*/
public function test_validation_jwks(): void {
$registration = json_decode($this->registrationfulljson, true);
$this->expectException(registration_exception::class);
$this->expectExceptionCode(400);
$registration['jwks_uri'] = '';
registration_helper::get()->registration_to_config($registration, 'TheClientId');
}
/**
* Validation Test: no domain nor targetlinkuri is rejected.
*/
public function test_validation_missing_domain_targetlinkuri(): void {
$registration = json_decode($this->registrationminimaljson, true);
$this->expectException(registration_exception::class);
$this->expectExceptionCode(400);
unset($registration['https://purl.imsglobal.org/spec/lti-tool-configuration']['domain']);
unset($registration['https://purl.imsglobal.org/spec/lti-tool-configuration']['target_link_uri']);
registration_helper::get()->registration_to_config($registration, 'TheClientId');
}
/**
* Validation Test: mismatch between domain and targetlinkuri is rejected.
*/
public function test_validation_domain_targetlinkuri_match(): void {
$registration = json_decode($this->registrationminimaljson, true);
$this->expectException(registration_exception::class);
$this->expectExceptionCode(400);
$registration['https://purl.imsglobal.org/spec/lti-tool-configuration']['domain'] = 'not.the.right.domain';
registration_helper::get()->registration_to_config($registration, 'TheClientId');
}
/**
* Validation Test: domain is required.
*/
public function test_validation_domain_targetlinkuri_onlylink(): void {
$registration = json_decode($this->registrationminimaljson, true);
unset($registration['https://purl.imsglobal.org/spec/lti-tool-configuration']['domain']);
$this->expectException(registration_exception::class);
$this->expectExceptionCode(400);
$config = registration_helper::get()->registration_to_config($registration, 'TheClientId');
}
/**
* Validation Test: base url (targetlinkuri) is built from domain if not present.
*/
public function test_validation_domain_targetlinkuri_onlydomain(): void {
$registration = json_decode($this->registrationminimaljson, true);
unset($registration['https://purl.imsglobal.org/spec/lti-tool-configuration']['target_link_uri']);
$config = registration_helper::get()->registration_to_config($registration, 'TheClientId');
$this->assertEquals('example.org', $config->lti_tooldomain);
$this->assertEquals('https://www.example.org', $config->lti_toolurl);
}
/**
* Test the transformation from lti config to OpenId LTI Client Registration response.
*/
public function test_config_to_registration(): void {
$orig = json_decode($this->registrationfulljson, true);
$orig['scope'] .= ' https://purl.imsglobal.org/spec/lti-nrps/scope/contextmembership.readonly';
$reghelper = registration_helper::get();
$reg = $reghelper->config_to_registration($reghelper->registration_to_config($orig, 'clid'), 12);
$this->assertEquals('clid', $reg['client_id']);
$this->assertEquals($orig['response_types'], $reg['response_types']);
$this->assertEquals($orig['initiate_login_uri'], $reg['initiate_login_uri']);
$this->assertEquals($orig['redirect_uris'], $reg['redirect_uris']);
$this->assertEquals($orig['jwks_uri'], $reg['jwks_uri']);
$this->assertEquals($orig['logo_uri'], $reg['logo_uri']);
$this->assertEquals('https://purl.imsglobal.org/spec/lti-ags/scope/score '.
'https://purl.imsglobal.org/spec/lti-ags/scope/result.readonly '.
'https://purl.imsglobal.org/spec/lti-ags/scope/lineitem.readonly '.
'https://purl.imsglobal.org/spec/lti-ags/scope/lineitem '.
'https://purl.imsglobal.org/spec/lti-nrps/scope/contextmembership.readonly', $reg['scope']);
$ltiorig = $orig['https://purl.imsglobal.org/spec/lti-tool-configuration'];
$lti = $reg['https://purl.imsglobal.org/spec/lti-tool-configuration'];
$this->assertEquals("12", $lti['deployment_id']);
$this->assertEquals($ltiorig['target_link_uri'], $lti['target_link_uri']);
$this->assertEquals($ltiorig['domain'], $lti['domain']);
$this->assertEquals($ltiorig['custom_parameters'], $lti['custom_parameters']);
$this->assertEquals($ltiorig['description'], $lti['description']);
$dlmsgorig = $ltiorig['messages'][0];
$dlmsg = $lti['messages'][0];
$this->assertEquals($dlmsgorig['type'], $dlmsg['type']);
$this->assertEquals($dlmsgorig['target_link_uri'], $dlmsg['target_link_uri']);
$this->assertTrue(in_array('iss', $lti['claims']));
$this->assertTrue(in_array('sub', $lti['claims']));
$this->assertTrue(in_array('email', $lti['claims']));
$this->assertTrue(in_array('family_name', $lti['claims']));
$this->assertTrue(in_array('given_name', $lti['claims']));
$this->assertTrue(in_array('name', $lti['claims']));
}
/**
* Test the transformation from lti config to OpenId LTI Client Registration response for the minimal version.
*/
public function test_config_to_registration_minimal(): void {
$orig = json_decode($this->registrationminimaljson, true);
$reghelper = registration_helper::get();
$reg = $reghelper->config_to_registration($reghelper->registration_to_config($orig, 'clid'), 12);
$this->assertEquals('clid', $reg['client_id']);
$this->assertEquals($orig['response_types'], $reg['response_types']);
$this->assertEquals($orig['initiate_login_uri'], $reg['initiate_login_uri']);
$this->assertEquals($orig['redirect_uris'], $reg['redirect_uris']);
$this->assertEquals($orig['jwks_uri'], $reg['jwks_uri']);
$this->assertEquals('', $reg['scope']);
$ltiorig = $orig['https://purl.imsglobal.org/spec/lti-tool-configuration'];
$lti = $reg['https://purl.imsglobal.org/spec/lti-tool-configuration'];
$this->assertTrue(in_array('iss', $lti['claims']));
$this->assertTrue(in_array('sub', $lti['claims']));
$this->assertFalse(in_array('email', $lti['claims']));
$this->assertFalse(in_array('family_name', $lti['claims']));
$this->assertFalse(in_array('given_name', $lti['claims']));
$this->assertFalse(in_array('name', $lti['claims']));
}
/**
* Test the transformation from lti config 1.1 to Registration Response.
*/
public function test_config_to_registration_lti11(): void {
$config = [];
$config['contentitem'] = 1;
$config['toolurl_ContentItemSelectionRequest'] = '';
$config['sendname'] = 0;
$config['sendemailaddr'] = 1;
$config['acceptgrades'] = 2;
$config['resourcekey'] = 'testkey';
$config['password'] = 'testp@ssw0rd';
$config['customparameters'] = 'a1=b1';
$type = [];
$type['id'] = 130;
$type['name'] = 'LTI Test 1.1';
$type['baseurl'] = 'https://base.test.url/test';
$type['tooldomain'] = 'base.test.url';
$type['ltiversion'] = 'LTI-1p0';
$type['icon'] = 'https://base.test.url/icon.png';
$reg = registration_helper::get()->config_to_registration((object)$config, $type['id'], (object)$type);
$this->assertFalse(isset($reg['client_id']));
$this->assertFalse(isset($reg['initiate_login_uri']));
$this->assertEquals($type['name'], $reg['client_name']);
$lti = $reg['https://purl.imsglobal.org/spec/lti-tool-configuration'];
$this->assertEquals(LTI_VERSION_1, $lti['version']);
$this->assertEquals('b1', $lti['custom_parameters']['a1']);
$this->assertEquals('LtiDeepLinkingRequest', $lti['messages'][0]['type']);
$this->assertEquals('base.test.url', $lti['domain']);
$this->assertEquals($type['baseurl'], $lti['target_link_uri']);
$oauth = $lti['oauth_consumer'];
$this->assertEquals('testkey', $oauth['key']);
$this->assertFalse(empty($oauth['nonce']));
$this->assertEquals(hash('sha256', 'testkeytestp@ssw0rd'.$oauth['nonce']), $oauth['sign']);
$this->assertTrue(in_array('iss', $lti['claims']));
$this->assertTrue(in_array('sub', $lti['claims']));
$this->assertTrue(in_array('email', $lti['claims']));
$this->assertFalse(in_array('family_name', $lti['claims']));
$this->assertFalse(in_array('given_name', $lti['claims']));
$this->assertFalse(in_array('name', $lti['claims']));
}
/**
* Test the transformation from lti config 2.0 to Registration Response.
* For LTI 2.0 we limit to just passing the previous key/secret.
*/
public function test_config_to_registration_lti20(): void {
$config = [];
$config['contentitem'] = 1;
$config['toolurl_ContentItemSelectionRequest'] = '';
$type = [];
$type['id'] = 131;
$type['name'] = 'LTI Test 1.2';
$type['baseurl'] = 'https://base.test.url/test';
$type['tooldomain'] = 'base.test.url';
$type['ltiversion'] = 'LTI-2p0';
$type['icon'] = 'https://base.test.url/icon.png';
$type['toolproxyid'] = 9;
$toolproxy = [];
$toolproxy['id'] = 9;
$toolproxy['guid'] = 'lti2guidtest';
$toolproxy['secret'] = 'peM7YDx420bo';
$reghelper = $this->getMockBuilder(registration_helper::class)
->setMethods(['get_tool_proxy'])
->getMock();
$map = [[$toolproxy['id'], $toolproxy]];
$reghelper->method('get_tool_proxy')
->will($this->returnValueMap($map));
$reg = $reghelper->config_to_registration((object)$config, $type['id'], (object)$type);
$this->assertFalse(isset($reg['client_id']));
$this->assertFalse(isset($reg['initiate_login_uri']));
$this->assertEquals($type['name'], $reg['client_name']);
$lti = $reg['https://purl.imsglobal.org/spec/lti-tool-configuration'];
$this->assertEquals(LTI_VERSION_2, $lti['version']);
$this->assertEquals('LtiDeepLinkingRequest', $lti['messages'][0]['type']);
$this->assertEquals('base.test.url', $lti['domain']);
$this->assertEquals($type['baseurl'], $lti['target_link_uri']);
$oauth = $lti['oauth_consumer'];
$this->assertEquals('lti2guidtest', $toolproxy['guid']);
$this->assertFalse(empty($oauth['nonce']));
$this->assertEquals(hash('sha256', 'lti2guidtestpeM7YDx420bo'.$oauth['nonce']), $oauth['sign']);
$this->assertTrue(in_array('iss', $lti['claims']));
$this->assertTrue(in_array('sub', $lti['claims']));
$this->assertFalse(in_array('email', $lti['claims']));
$this->assertFalse(in_array('family_name', $lti['claims']));
$this->assertFalse(in_array('given_name', $lti['claims']));
$this->assertFalse(in_array('name', $lti['claims']));
}
}
+272
View File
@@ -0,0 +1,272 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
//
// This file is part of BasicLTI4Moodle
//
// BasicLTI4Moodle is an IMS BasicLTI (Basic Learning Tools for Interoperability)
// consumer for Moodle 1.9 and Moodle 2.0. BasicLTI is a IMS Standard that allows web
// based learning tools to be easily integrated in LMS as native ones. The IMS BasicLTI
// specification is part of the IMS standard Common Cartridge 1.1 Sakai and other main LMS
// are already supporting or going to support BasicLTI. This project Implements the consumer
// for Moodle. Moodle is a Free Open source Learning Management System by Martin Dougiamas.
// BasicLTI4Moodle is a project iniciated and leaded by Ludo(Marc Alier) and Jordi Piguillem
// at the GESSI research group at UPC.
// SimpleLTI consumer for Moodle is an implementation of the early specification of LTI
// by Charles Severance (Dr Chuck) htp://dr-chuck.com , developed by Jordi Piguillem in a
// Google Summer of Code 2008 project co-mentored by Charles Severance and Marc Alier.
//
// BasicLTI4Moodle is copyright 2009 by Marc Alier Forment, Jordi Piguillem and Nikolas Galanis
// of the Universitat Politecnica de Catalunya http://www.upc.edu
// Contact info: Marc Alier Forment granludo @ gmail.com or marc.alier @ upc.edu.
namespace mod_lti\local;
use mod_lti_testcase;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->dirroot . '/mod/lti/locallib.php');
require_once($CFG->dirroot . '/mod/lti/tests/mod_lti_testcase.php');
/**
* Types helper tests.
*
* @package mod_lti
* @copyright 2023 Jake Dallimore <jrhdallimore@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @coversDefaultClass \mod_lti\local\types_helper
*/
class types_helper_test extends mod_lti_testcase {
/**
* Test fetching tool types for a given course and user.
*
* @covers ::get_lti_types_by_course
* @return void.
*/
public function test_get_lti_types_by_course(): void {
$this->resetAfterTest();
global $DB;
$coursecat1 = $this->getDataGenerator()->create_category();
$coursecat2 = $this->getDataGenerator()->create_category();
$course = $this->getDataGenerator()->create_course(['category' => $coursecat1->id]);
$course2 = $this->getDataGenerator()->create_course(['category' => $coursecat2->id]);
$teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
$teacher2 = $this->getDataGenerator()->create_and_enrol($course2, 'editingteacher');
$this->setUser($teacher);
// Create the following tool types for testing:
// - Site tool configured as "Do not show" (LTI_COURSEVISIBLE_NO).
// - Site tool configured as "Show as a preconfigured tool only" (LTI_COURSEVISIBLE_PRECONFIGURED).
// - Site tool configured as "Show as a preconfigured tool and in the activity chooser" (LTI_COURSEVISIBLE_ACTIVITYCHOOSER).
// - Course tool which, by default, is configured as LTI_COURSEVISIBLE_ACTIVITYCHOOSER).
// - Site tool configured to "Show as a preconfigured tool and in the activity chooser" but restricted to a category.
/** @var \mod_lti_generator $ltigenerator */
$ltigenerator = $this->getDataGenerator()->get_plugin_generator('mod_lti');
$ltigenerator->create_tool_types([
'name' => 'site tool do not show',
'baseurl' => 'http://example.com/tool/1',
'coursevisible' => LTI_COURSEVISIBLE_NO,
'state' => LTI_TOOL_STATE_CONFIGURED
]);
$ltigenerator->create_tool_types([
'name' => 'site tool preconfigured only',
'baseurl' => 'http://example.com/tool/2',
'coursevisible' => LTI_COURSEVISIBLE_PRECONFIGURED,
'state' => LTI_TOOL_STATE_CONFIGURED
]);
$ltigenerator->create_tool_types([
'name' => 'site tool preconfigured and activity chooser',
'baseurl' => 'http://example.com/tool/3',
'coursevisible' => LTI_COURSEVISIBLE_ACTIVITYCHOOSER,
'state' => LTI_TOOL_STATE_CONFIGURED
]);
$ltigenerator->create_course_tool_types([
'name' => 'course tool preconfigured and activity chooser',
'baseurl' => 'http://example.com/tool/4',
'course' => $course->id
]);
$ltigenerator->create_tool_types([
'name' => 'site tool preconfigured and activity chooser, restricted to category 2',
'baseurl' => 'http://example.com/tool/5',
'coursevisible' => LTI_COURSEVISIBLE_ACTIVITYCHOOSER,
'state' => LTI_TOOL_STATE_CONFIGURED,
'lti_coursecategories' => $coursecat2->id
]);
// Request using the default 'coursevisible' param will include all tools except the one configured as "Do not show" and
// the tool restricted to category 2.
$coursetooltypes = types_helper::get_lti_types_by_course($course->id, $teacher->id);
$this->assertCount(3, $coursetooltypes);
$expected = [
'http://example.com/tool/2',
'http://example.com/tool/3',
'http://example.com/tool/4',
];
sort($expected);
$actual = array_column($coursetooltypes, 'baseurl');
sort($actual);
$this->assertEquals($expected, $actual);
// Request for only those tools configured to show in the activity chooser for the teacher.
$coursetooltypes = types_helper::get_lti_types_by_course($course->id, $teacher->id,
[LTI_COURSEVISIBLE_ACTIVITYCHOOSER]);
$this->assertCount(2, $coursetooltypes);
$expected = [
'http://example.com/tool/3',
'http://example.com/tool/4',
];
sort($expected);
$actual = array_column($coursetooltypes, 'baseurl');
sort($actual);
$this->assertEquals($expected, $actual);
// Request for only those tools configured to show as a preconfigured tool for the teacher.
$coursetooltypes = types_helper::get_lti_types_by_course($course->id, $teacher->id,
[LTI_COURSEVISIBLE_PRECONFIGURED]);
$this->assertCount(1, $coursetooltypes);
$expected = [
'http://example.com/tool/2',
];
$actual = array_column($coursetooltypes, 'baseurl');
$this->assertEquals($expected, $actual);
// Request for teacher2 in course2 (course category 2).
$coursetooltypes = types_helper::get_lti_types_by_course($course2->id, $teacher2->id);
$this->assertCount(3, $coursetooltypes);
$expected = [
'http://example.com/tool/2',
'http://example.com/tool/3',
'http://example.com/tool/5',
];
sort($expected);
$actual = array_column($coursetooltypes, 'baseurl');
sort($actual);
$this->assertEquals($expected, $actual);
// Request for a teacher who cannot use preconfigured tools in the course.
$teacherrole = $DB->get_record('role', array('shortname' => 'editingteacher'));
assign_capability('mod/lti:addpreconfiguredinstance', CAP_PROHIBIT, $teacherrole->id,
\core\context\course::instance($course->id));
$coursetooltypes = types_helper::get_lti_types_by_course($course->id, $teacher->id);
$this->assertCount(0, $coursetooltypes);
}
/**
* Test fetching tool types for a given course and user.
*
* @covers ::override_type_showinactivitychooser
* @return void.
*/
public function test_override_type_showinactivitychooser(): void {
$this->resetAfterTest();
global $DB;
$coursecat1 = $this->getDataGenerator()->create_category();
$coursecat2 = $this->getDataGenerator()->create_category();
$course = $this->getDataGenerator()->create_course(['category' => $coursecat1->id]);
$course2 = $this->getDataGenerator()->create_course(['category' => $coursecat2->id]);
$teacher = $this->getDataGenerator()->create_and_enrol($course, 'editingteacher');
$teacher2 = $this->getDataGenerator()->create_and_enrol($course2, 'editingteacher');
$context = \core\context\course::instance($course->id);
$this->setUser($teacher);
/*
Create the following tool types for testing:
| tooltype | coursevisible | restrictedtocategory |
| site | LTI_COURSEVISIBLE_NO | |
| site | LTI_COURSEVISIBLE_PRECONFIGURED | |
| site | LTI_COURSEVISIBLE_ACTIVITYCHOOSER | yes |
| site | LTI_COURSEVISIBLE_ACTIVITYCHOOSER | yes |
| course | LTI_COURSEVISIBLE_ACTIVITYCHOOSER | |
*/
/** @var \mod_lti_generator $ltigenerator */
$ltigenerator = $this->getDataGenerator()->get_plugin_generator('mod_lti');
$tool1id = $ltigenerator->create_tool_types([
'name' => 'site tool do not show',
'baseurl' => 'http://example.com/tool/1',
'coursevisible' => LTI_COURSEVISIBLE_NO,
'state' => LTI_TOOL_STATE_CONFIGURED
]);
$tool2id = $ltigenerator->create_tool_types([
'name' => 'site tool preconfigured only',
'baseurl' => 'http://example.com/tool/2',
'coursevisible' => LTI_COURSEVISIBLE_PRECONFIGURED,
'state' => LTI_TOOL_STATE_CONFIGURED
]);
$tool3id = $ltigenerator->create_course_tool_types([
'name' => 'course tool preconfigured and activity chooser',
'baseurl' => 'http://example.com/tool/3',
'course' => $course->id
]);
$tool4id = $ltigenerator->create_tool_types([
'name' => 'site tool preconfigured and activity chooser, restricted to category 2',
'baseurl' => 'http://example.com/tool/4',
'coursevisible' => LTI_COURSEVISIBLE_ACTIVITYCHOOSER,
'state' => LTI_TOOL_STATE_CONFIGURED,
'lti_coursecategories' => $coursecat2->id
]);
$tool5id = $ltigenerator->create_tool_types([
'name' => 'site tool preconfigured and activity chooser, restricted to category 1',
'baseurl' => 'http://example.com/tool/5',
'coursevisible' => LTI_COURSEVISIBLE_ACTIVITYCHOOSER,
'state' => LTI_TOOL_STATE_CONFIGURED,
'lti_coursecategories' => $coursecat1->id
]);
// LTI_COURSEVISIBLE_NO can't be updated.
$result = types_helper::override_type_showinactivitychooser($tool1id, $course->id, $context, true);
$this->assertFalse($result);
// Tool not exist.
$result = types_helper::override_type_showinactivitychooser($tool5id + 1, $course->id, $context, false);
$this->assertFalse($result);
$result = types_helper::override_type_showinactivitychooser($tool2id, $course->id, $context, true);
$this->assertTrue($result);
$coursevisibleoverriden = $DB->get_field('lti_coursevisible', 'coursevisible',
['typeid' => $tool2id, 'courseid' => $course->id]);
$this->assertEquals(LTI_COURSEVISIBLE_ACTIVITYCHOOSER, $coursevisibleoverriden);
$result = types_helper::override_type_showinactivitychooser($tool3id, $course->id, $context, false);
$this->assertTrue($result);
$coursevisible = $DB->get_field('lti_types', 'coursevisible', ['id' => $tool3id]);
$this->assertEquals(LTI_COURSEVISIBLE_PRECONFIGURED, $coursevisible);
// Restricted category no allowed.
$this->expectException('moodle_exception');
$this->expectExceptionMessage('You are not allowed to change this setting for this tool.');
types_helper::override_type_showinactivitychooser($tool4id, $course->id, $context, false);
// Restricted category allowed.
$result = types_helper::override_type_showinactivitychooser($tool5id, $course->id, $context, false);
$this->assertTrue($result);
$coursevisibleoverriden = $DB->get_field('lti_coursevisible', 'coursevisible',
['typeid' => $tool5id, 'courseid' => $course->id]);
$this->assertEquals(LTI_COURSEVISIBLE_PRECONFIGURED, $coursevisibleoverriden);
$this->setUser($teacher2);
$this->expectException(\required_capability_exception::class);
types_helper::override_type_showinactivitychooser($tool5id, $course->id, $context, false);
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,107 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Unit tests for mod_lti edit_form
*
* @package mod_lti
* @copyright 2023 Jackson D'Souza <jackson.dsouza@catalyst-eu.net>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since Moodle 4.3
*/
defined('MOODLE_INTERNAL') || die();
require_once(__DIR__ . '/course_categories_trait.php');
/**
* Unit tests for mod_lti edit_form
*
* @package mod_lti
* @copyright 2023 Jackson D'Souza <jackson.dsouza@catalyst-eu.net>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since Moodle 4.3
*/
class mod_lti_edit_types_form_test extends \advanced_testcase {
// There are shared helpers for these tests in the helper course_categories_trait.
use \mod_lti_course_categories_trait;
/**
* Tests the nested course categories JSON returned by public method mod_lti_edit_types_form::lti_build_category_tree().
*
* @covers \mod_lti_edit_types_form::lti_build_category_tree
*/
public function test_set_nested_categories(): void {
global $CFG, $DB;
require_once($CFG->dirroot . '/mod/lti/tests/fixtures/test_edit_form.php');
$this->resetAfterTest(true);
$this->setAdminUser();
$ltiform = new test_edit_form(null);
$ltiform->definition_after_data();
// Setup fixture.
$coursecategories = $this->setup_course_categories();
$categoryarray[] = [
"id" => $coursecategories['topcat']->id,
"parent" => $coursecategories['topcat']->parent,
"name" => $coursecategories['topcat']->name,
"nodes" => [
[
"id" => $coursecategories['subcata']->id,
"parent" => $coursecategories['topcat']->id,
"name" => $coursecategories['subcata']->name,
"nodes" => [
[
"id" => $coursecategories['subcatca']->id,
"parent" => $coursecategories['subcata']->id,
"name" => $coursecategories['subcatca']->name,
"nodes" => "",
"haschildren" => ""
]
],
"haschildren" => "1"
],
[
"id" => $coursecategories['subcatb']->id,
"parent" => $coursecategories['topcat']->id,
"name" => $coursecategories['subcatb']->name,
"nodes" => [
[
"id" => $coursecategories['subcatcb']->id,
"parent" => $coursecategories['subcatb']->id,
"name" => $coursecategories['subcatcb']->name,
"nodes" => "",
"haschildren" => ""
]
],
"haschildren" => "1"
]
],
"haschildren" => "1"
];
$records = $DB->get_records('course_categories', [], 'sortorder, id', 'id, parent, name');
$allcategories = json_decode(json_encode($records), true);
$coursecategoriesarray = $ltiform->lti_build_category_tree($allcategories);
$this->assertEquals($categoryarray, $coursecategoriesarray);
}
}
+71
View File
@@ -0,0 +1,71 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
use core_external\external_api;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->dirroot . '/webservice/tests/helpers.php');
require_once($CFG->dirroot . '/mod/lti/locallib.php');
/**
* Abstract base testcase for mod_lti unit tests.
*
* @package mod_lti
* @author Andrew Madden <andrewmadden@catalyst-au.net>
* @copyright 2020 Catalyst IT
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
abstract class mod_lti_testcase extends externallib_advanced_testcase {
/**
* Generate a tool type.
*
* @param string $uniqueid Each tool type needs a different base url. Provide a unique string for every tool type created.
* @param int|null $toolproxyid Optional proxy to associate with tool type.
* @return stdClass A tool type.
*/
protected function generate_tool_type(string $uniqueid, ?int $toolproxyid = null): stdClass {
// Create a tool type.
$type = new stdClass();
$type->state = LTI_TOOL_STATE_CONFIGURED;
$type->name = "Test tool $uniqueid";
$type->description = "Example description $uniqueid";
$type->toolproxyid = $toolproxyid;
$type->baseurl = $this->getExternalTestFileUrl("/test$uniqueid.html");
$type->coursevisible = LTI_COURSEVISIBLE_ACTIVITYCHOOSER;
$config = new stdClass();
$config->lti_coursevisible = LTI_COURSEVISIBLE_ACTIVITYCHOOSER;
$type->id = lti_add_type($type, $config);
return $type;
}
/**
* Generate a tool proxy.
*
* @param string $uniqueid Each tool proxy needs a different reg url. Provide a unique string for every tool proxy created.
* @return stdClass A tool proxy.
*/
protected function generate_tool_proxy(string $uniqueid): stdClass {
// Create a tool proxy.
$proxy = mod_lti_external::create_tool_proxy("Test proxy $uniqueid",
$this->getExternalTestFileUrl("/proxy$uniqueid.html"), [], []);
return (object)external_api::clean_returnvalue(mod_lti_external::create_tool_proxy_returns(), $proxy);
}
}
+409
View File
@@ -0,0 +1,409 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Privacy provider tests.
*
* @package mod_lti
* @copyright 2018 Mark Nelson <markn@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace mod_lti\privacy;
use core_privacy\local\metadata\collection;
use core_privacy\local\request\approved_contextlist;
use core_privacy\local\request\approved_userlist;
use mod_lti\privacy\provider;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->dirroot . '/mod/lti/locallib.php');
/**
* Privacy provider tests class.
*
* @package mod_lti
* @copyright 2018 Mark Nelson <markn@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class provider_test extends \core_privacy\tests\provider_testcase {
/**
* Test for provider::get_metadata().
*/
public function test_get_metadata(): void {
$collection = new collection('mod_lti');
$newcollection = provider::get_metadata($collection);
$itemcollection = $newcollection->get_collection();
$this->assertCount(4, $itemcollection);
$ltiproviderexternal = array_shift($itemcollection);
$this->assertEquals('lti_provider', $ltiproviderexternal->get_name());
$ltisubmissiontable = array_shift($itemcollection);
$this->assertEquals('lti_submission', $ltisubmissiontable->get_name());
$ltitoolproxies = array_shift($itemcollection);
$this->assertEquals('lti_tool_proxies', $ltitoolproxies->get_name());
$ltitypestable = array_shift($itemcollection);
$this->assertEquals('lti_types', $ltitypestable->get_name());
$privacyfields = $ltisubmissiontable->get_privacy_fields();
$this->assertArrayHasKey('userid', $privacyfields);
$this->assertArrayHasKey('datesubmitted', $privacyfields);
$this->assertArrayHasKey('dateupdated', $privacyfields);
$this->assertArrayHasKey('gradepercent', $privacyfields);
$this->assertArrayHasKey('originalgrade', $privacyfields);
$this->assertEquals('privacy:metadata:lti_submission', $ltisubmissiontable->get_summary());
$privacyfields = $ltitoolproxies->get_privacy_fields();
$this->assertArrayHasKey('name', $privacyfields);
$this->assertArrayHasKey('createdby', $privacyfields);
$this->assertArrayHasKey('timecreated', $privacyfields);
$this->assertArrayHasKey('timemodified', $privacyfields);
$this->assertEquals('privacy:metadata:lti_tool_proxies', $ltitoolproxies->get_summary());
$privacyfields = $ltitypestable->get_privacy_fields();
$this->assertArrayHasKey('name', $privacyfields);
$this->assertArrayHasKey('createdby', $privacyfields);
$this->assertArrayHasKey('timecreated', $privacyfields);
$this->assertArrayHasKey('timemodified', $privacyfields);
$this->assertEquals('privacy:metadata:lti_types', $ltitypestable->get_summary());
}
/**
* Test for provider::get_contexts_for_userid().
*/
public function test_get_contexts_for_userid(): void {
$this->resetAfterTest();
$course = $this->getDataGenerator()->create_course();
// The LTI activity the user will have submitted something for.
$lti = $this->getDataGenerator()->create_module('lti', array('course' => $course->id));
// Another LTI activity that has no user activity.
$this->getDataGenerator()->create_module('lti', array('course' => $course->id));
// Create a user which will make a submission.
$user = $this->getDataGenerator()->create_user();
$this->create_lti_submission($lti->id, $user->id);
// Check the contexts supplied are correct.
$contextlist = provider::get_contexts_for_userid($user->id);
$this->assertCount(2, $contextlist);
$contextformodule = $contextlist->current();
$cmcontext = \context_module::instance($lti->cmid);
$this->assertEquals($cmcontext->id, $contextformodule->id);
$contextlist->next();
$contextforsystem = $contextlist->current();
$this->assertEquals(SYSCONTEXTID, $contextforsystem->id);
}
/**
* Test for provider::test_get_users_in_context()
*/
public function test_get_users_in_context(): void {
$this->resetAfterTest();
$course = $this->getDataGenerator()->create_course();
$component = 'mod_lti';
// The LTI activity the user will have submitted something for.
$lti1 = $this->getDataGenerator()->create_module('lti', array('course' => $course->id));
// Another LTI activity that has no user activity.
$lti2 = $this->getDataGenerator()->create_module('lti', array('course' => $course->id));
// Create user which will make a submission each.
$user1 = $this->getDataGenerator()->create_user();
$user2 = $this->getDataGenerator()->create_user();
$this->create_lti_submission($lti1->id, $user1->id);
$this->create_lti_submission($lti1->id, $user2->id);
$context = \context_module::instance($lti1->cmid);
$userlist = new \core_privacy\local\request\userlist($context, $component);
provider::get_users_in_context($userlist);
$this->assertCount(2, $userlist);
$expected = [$user1->id, $user2->id];
$actual = $userlist->get_userids();
sort($expected);
sort($actual);
$this->assertEquals($expected, $actual);
$context = \context_module::instance($lti2->cmid);
$userlist = new \core_privacy\local\request\userlist($context, $component);
provider::get_users_in_context($userlist);
$this->assertEmpty($userlist);
}
/**
* Test for provider::export_user_data().
*/
public function test_export_for_context_submissions(): void {
$this->resetAfterTest();
$course = $this->getDataGenerator()->create_course();
$lti = $this->getDataGenerator()->create_module('lti', array('course' => $course->id));
// Create users which will make submissions.
$user1 = $this->getDataGenerator()->create_user();
$user2 = $this->getDataGenerator()->create_user();
$this->create_lti_submission($lti->id, $user1->id);
$this->create_lti_submission($lti->id, $user1->id);
$this->create_lti_submission($lti->id, $user2->id);
// Export all of the data for the context for user 1.
$cmcontext = \context_module::instance($lti->cmid);
$this->export_context_data_for_user($user1->id, $cmcontext, 'mod_lti');
$writer = \core_privacy\local\request\writer::with_context($cmcontext);
$this->assertTrue($writer->has_any_data());
$data = $writer->get_data();
$this->assertCount(2, $data->submissions);
}
/**
* Test for provider::export_user_data().
*/
public function test_export_for_context_tool_types(): void {
$this->resetAfterTest();
$course1 = $this->getDataGenerator()->create_course();
$course2 = $this->getDataGenerator()->create_course();
// Create a user which will make a tool type.
$user = $this->getDataGenerator()->create_user();
$this->setUser($user);
// Create a user that will not make a tool type.
$this->getDataGenerator()->create_user();
$type = new \stdClass();
$type->baseurl = 'http://moodle.org';
$type->course = $course1->id;
lti_add_type($type, new \stdClass());
$type = new \stdClass();
$type->baseurl = 'http://moodle.org';
$type->course = $course1->id;
lti_add_type($type, new \stdClass());
$type = new \stdClass();
$type->baseurl = 'http://moodle.org';
$type->course = $course2->id;
lti_add_type($type, new \stdClass());
// Export all of the data for the context.
$coursecontext = \context_course::instance($course1->id);
$this->export_context_data_for_user($user->id, $coursecontext, 'mod_lti');
$writer = \core_privacy\local\request\writer::with_context($coursecontext);
$this->assertTrue($writer->has_any_data());
$data = $writer->get_data();
$this->assertCount(2, $data->lti_types);
$coursecontext = \context_course::instance($course2->id);
$this->export_context_data_for_user($user->id, $coursecontext, 'mod_lti');
$writer = \core_privacy\local\request\writer::with_context($coursecontext);
$this->assertTrue($writer->has_any_data());
$data = $writer->get_data();
$this->assertCount(1, $data->lti_types);
}
/**
* Test for provider::export_user_data().
*/
public function test_export_for_context_tool_proxies(): void {
$this->resetAfterTest();
// Create a user that will not make a tool proxy.
$user = $this->getDataGenerator()->create_user();
$this->setUser($user);
$toolproxy = new \stdClass();
$toolproxy->createdby = $user;
lti_add_tool_proxy($toolproxy);
// Export all of the data for the context.
$systemcontext = \context_system::instance();
$this->export_context_data_for_user($user->id, $systemcontext, 'mod_lti');
$writer = \core_privacy\local\request\writer::with_context($systemcontext);
$this->assertTrue($writer->has_any_data());
$data = $writer->get_data();
$this->assertCount(1, $data->lti_tool_proxies);
}
/**
* Test for provider::delete_data_for_all_users_in_context().
*/
public function test_delete_data_for_all_users_in_context(): void {
global $DB;
$this->resetAfterTest();
$course = $this->getDataGenerator()->create_course();
$lti = $this->getDataGenerator()->create_module('lti', array('course' => $course->id));
// Create users that will make submissions.
$user1 = $this->getDataGenerator()->create_user();
$user2 = $this->getDataGenerator()->create_user();
$this->create_lti_submission($lti->id, $user1->id);
$this->create_lti_submission($lti->id, $user2->id);
// Before deletion, we should have 2 responses.
$count = $DB->count_records('lti_submission', ['ltiid' => $lti->id]);
$this->assertEquals(2, $count);
// Delete data based on context.
$cmcontext = \context_module::instance($lti->cmid);
provider::delete_data_for_all_users_in_context($cmcontext);
// After deletion, the lti submissions for that lti activity should have been deleted.
$count = $DB->count_records('lti_submission', ['ltiid' => $lti->id]);
$this->assertEquals(0, $count);
}
/**
* Test for provider::delete_data_for_user().
*/
public function test_delete_data_for_user(): void {
global $DB;
$this->resetAfterTest();
$course = $this->getDataGenerator()->create_course();
$lti = $this->getDataGenerator()->create_module('lti', array('course' => $course->id));
// Create users that will make submissions.
$user1 = $this->getDataGenerator()->create_user();
$user2 = $this->getDataGenerator()->create_user();
$this->create_lti_submission($lti->id, $user1->id);
$this->create_lti_submission($lti->id, $user2->id);
// Before deletion we should have 2 responses.
$count = $DB->count_records('lti_submission', ['ltiid' => $lti->id]);
$this->assertEquals(2, $count);
$context = \context_module::instance($lti->cmid);
$contextlist = new approved_contextlist($user1, 'lti',
[\context_system::instance()->id, $context->id]);
provider::delete_data_for_user($contextlist);
// After deletion the lti submission for the first user should have been deleted.
$count = $DB->count_records('lti_submission', ['ltiid' => $lti->id, 'userid' => $user1->id]);
$this->assertEquals(0, $count);
// Check the submission for the other user is still there.
$ltisubmission = $DB->get_records('lti_submission');
$this->assertCount(1, $ltisubmission);
$lastsubmission = reset($ltisubmission);
$this->assertEquals($user2->id, $lastsubmission->userid);
}
/**
* Test for provider::delete_data_for_users().
*/
public function test_delete_data_for_users(): void {
global $DB;
$component = 'mod_lti';
$this->resetAfterTest();
$course = $this->getDataGenerator()->create_course();
$lti = $this->getDataGenerator()->create_module('lti', array('course' => $course->id));
// Create users that will make submissions.
$user1 = $this->getDataGenerator()->create_user();
$user2 = $this->getDataGenerator()->create_user();
$user3 = $this->getDataGenerator()->create_user();
$this->create_lti_submission($lti->id, $user1->id);
$this->create_lti_submission($lti->id, $user2->id);
$this->create_lti_submission($lti->id, $user3->id);
// Before deletion we should have 2 responses.
$count = $DB->count_records('lti_submission', ['ltiid' => $lti->id]);
$this->assertEquals(3, $count);
$context = \context_module::instance($lti->cmid);
$approveduserids = [$user1->id, $user2->id];
$approvedlist = new approved_userlist($context, $component, $approveduserids);
provider::delete_data_for_users($approvedlist);
// After deletion the lti submission for the first two users should have been deleted.
list($insql, $inparams) = $DB->get_in_or_equal($approveduserids, SQL_PARAMS_NAMED);
$sql = "ltiid = :ltiid AND userid {$insql}";
$params = array_merge($inparams, ['ltiid' => $lti->id]);
$count = $DB->count_records_select('lti_submission', $sql, $params);
$this->assertEquals(0, $count);
// Check the submission for the third user is still there.
$ltisubmission = $DB->get_records('lti_submission');
$this->assertCount(1, $ltisubmission);
$lastsubmission = reset($ltisubmission);
$this->assertEquals($user3->id, $lastsubmission->userid);
}
/**
* Mimicks the creation of an LTI submission.
*
* There is no API we can use to insert an LTI submission, so we
* will simply insert directly into the database.
*
* @param int $ltiid
* @param int $userid
*/
protected function create_lti_submission(int $ltiid, int $userid) {
global $DB;
$ltisubmissiondata = [
'ltiid' => $ltiid,
'userid' => $userid,
'datesubmitted' => time(),
'dateupdated' => time(),
'gradepercent' => 65,
'originalgrade' => 70,
'launchid' => 3,
'state' => 1
];
$DB->insert_record('lti_submission', $ltisubmissiondata);
}
}
@@ -0,0 +1,75 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace mod_lti;
/**
* Tests Exception handler for LTI services
*
* @package mod_lti
* @copyright Copyright (c) 2015 Moodlerooms Inc. (http://www.moodlerooms.com)
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class service_exception_handler_test extends \advanced_testcase {
/**
* Testing service error handling.
*/
public function test_handle(): void {
$handler = new service_exception_handler(false);
$handler->set_message_id('123');
$handler->set_message_type('testRequest');
$handler->handle(new \Exception('Error happened'));
$this->expectOutputRegex('/imsx_codeMajor>failure/');
$this->expectOutputRegex('/imsx_description>Error happened/');
$this->expectOutputRegex('/imsx_messageRefIdentifier>123/');
$this->expectOutputRegex('/imsx_operationRefIdentifier>testRequest/');
$this->expectOutputRegex('/imsx_POXBody><testResponse/');
}
/**
* Testing service error handling when message ID and type are not known yet.
*/
public function test_handle_early_error(): void {
$handler = new service_exception_handler(false);
$handler->handle(new \Exception('Error happened'));
$this->expectOutputRegex('/imsx_codeMajor>failure/');
$this->expectOutputRegex('/imsx_description>Error happened/');
$this->expectOutputRegex('/imsx_messageRefIdentifier\/>/');
$this->expectOutputRegex('/imsx_operationRefIdentifier>unknownRequest/');
$this->expectOutputRegex('/imsx_POXBody><unknownResponse/');
}
/**
* Testing that a log file is generated when logging is turned on.
*/
public function test_handle_log(): void {
global $CFG;
$this->resetAfterTest();
$handler = new service_exception_handler(true);
ob_start();
$handler->handle(new \Exception('Error happened'));
ob_end_clean();
$this->assertTrue(is_dir($CFG->dataroot.'/temp/mod_lti'));
$files = glob($CFG->dataroot.'/temp/mod_lti/mod*');
$this->assertEquals(1, count($files));
}
}
+109
View File
@@ -0,0 +1,109 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace mod_lti;
defined('MOODLE_INTERNAL') || die();
global $CFG;
require_once($CFG->dirroot.'/mod/lti/servicelib.php');
/**
* Tests for servicelib.php
*
* @package mod_lti
* @copyright Copyright (c) 2015 Moodlerooms Inc. (http://www.moodlerooms.com)
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class servicelib_test extends \basic_testcase {
/**
* Test that lti_parse_message_id never fails with good and bad XML.
*
* @dataProvider message_id_provider
* @param mixed $expected Expected message ID.
* @param string $xml XML to parse.
*/
public function test_lti_parse_message_id($expected, $xml): void {
$xml = simplexml_load_string($xml);
$this->assertEquals($expected, lti_parse_message_id($xml));
}
/**
* Test data provider for testing lti_parse_message_id
*
* @return array
*/
public function message_id_provider() {
$valid = <<<XML
<?xml version="1.0" encoding="UTF-8"?>
<imsx_POXEnvelopeRequest xmlns="http://www.imsglobal.org/services/ltiv1p1/xsd/imsoms_v1p0">
<imsx_POXHeader>
<imsx_POXRequestHeaderInfo>
<imsx_version>V1.0</imsx_version>
<imsx_messageIdentifier>9999</imsx_messageIdentifier>
</imsx_POXRequestHeaderInfo>
</imsx_POXHeader>
<imsx_POXBody/>
</imsx_POXEnvelopeRequest>
XML;
$noheader = <<<XML
<?xml version="1.0" encoding="UTF-8"?>
<imsx_POXEnvelopeRequest xmlns="http://www.imsglobal.org/services/ltiv1p1/xsd/imsoms_v1p0">
<badXmlHere>
<imsx_POXRequestHeaderInfo>
<imsx_version>V1.0</imsx_version>
<imsx_messageIdentifier>9999</imsx_messageIdentifier>
</imsx_POXRequestHeaderInfo>
</badXmlHere>
<imsx_POXBody/>
</imsx_POXEnvelopeRequest>
XML;
$noinfo = <<<XML
<?xml version="1.0" encoding="UTF-8"?>
<imsx_POXEnvelopeRequest xmlns="http://www.imsglobal.org/services/ltiv1p1/xsd/imsoms_v1p0">
<imsx_POXHeader>
<badXmlHere>
<imsx_version>V1.0</imsx_version>
<imsx_messageIdentifier>9999</imsx_messageIdentifier>
</badXmlHere>
</imsx_POXHeader>
<imsx_POXBody/>
</imsx_POXEnvelopeRequest>
XML;
$noidentifier = <<<XML
<?xml version="1.0" encoding="UTF-8"?>
<imsx_POXEnvelopeRequest xmlns="http://www.imsglobal.org/services/ltiv1p1/xsd/imsoms_v1p0">
<imsx_POXHeader>
<imsx_POXRequestHeaderInfo>
<imsx_version>V1.0</imsx_version>
</imsx_POXRequestHeaderInfo>
</imsx_POXHeader>
<imsx_POXBody/>
</imsx_POXEnvelopeRequest>
XML;
return array(
array(9999, $valid),
array('', $noheader),
array('', $noinfo),
array('', $noidentifier),
);
}
}
@@ -0,0 +1,75 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
namespace mod_lti\task;
/**
* Tests cleaning up the access tokens task.
*
* @package mod_lti
* @category test
* @copyright 2019 Mark Nelson <markn@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class clean_access_tokens_test extends \advanced_testcase {
/**
* Test set up.
*
* This is executed before running any test in this file.
*/
public function setUp(): void {
$this->resetAfterTest();
}
/**
* Test the cleanup task.
*/
public function test_cleanup_task(): void {
global $DB;
$time = time();
// Create an expired access token.
$token = new \stdClass();
$token->typeid = 1;
$token->scope = 'scope';
$token->token = 'token';
$token->validuntil = $time - DAYSECS;
$token->timecreated = $time - DAYSECS;
$t1id = $DB->insert_record('lti_access_tokens', $token);
// New token, in the future.
$token->validuntil = $time + DAYSECS;
$token->token = 'token2';
$t2id = $DB->insert_record('lti_access_tokens', $token);
// Run the task.
$task = new clean_access_tokens();
$task->execute();
// Check there is only one token now.
$tokens = $DB->get_records('lti_access_tokens');
$this->assertCount(1, $tokens);
$token = reset($tokens);
$this->assertEquals($t2id, $token->id);
}
}