Proyectos de Subversion Moodle

Rev

Rev 11 | | Comparar con el anterior | Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 1
<?php
2
// This file is part of Moodle - http://moodle.org/
3
//
4
// Moodle is free software: you can redistribute it and/or modify
5
// it under the terms of the GNU General Public License as published by
6
// the Free Software Foundation, either version 3 of the License, or
7
// (at your option) any later version.
8
//
9
// Moodle is distributed in the hope that it will be useful,
10
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
// GNU General Public License for more details.
13
//
14
// You should have received a copy of the GNU General Public License
15
// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
16
 
17
namespace ltiservice_gradebookservices;
18
 
19
use ltiservice_gradebookservices\local\resources\lineitem;
1441 ariadna 20
use ltiservice_gradebookservices\local\resources\results;
21
use ltiservice_gradebookservices\local\resources\scores;
1 efrain 22
use ltiservice_gradebookservices\local\service\gradebookservices;
23
 
24
/**
25
 * Unit tests for lti lineitem.
26
 *
27
 * @package    ltiservice_gradebookservices
28
 * @category   test
29
 * @copyright  2022 Cengage Group <claude.vervoort@cengage.com>
30
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
31
 * @coversDefaultClass \mod_lti\service\gradebookservices\local\resources\lineitem
32
 */
1441 ariadna 33
final class lineitem_test extends \advanced_testcase {
1 efrain 34
 
35
    /**
36
     * @covers ::execute
37
     *
38
     * Test updating the line item with submission review.
39
     */
11 efrain 40
    public function test_execute_put_nosubreview(): void {
1 efrain 41
        global $CFG;
42
        require_once($CFG->dirroot . '/mod/lti/locallib.php');
43
        $this->resetAfterTest();
44
        $this->setAdminUser();
45
        $resourceid = 'test-resource-id';
46
        $tag = 'tag';
47
        $course = $this->getDataGenerator()->create_course();
48
        $typeid = $this->create_type();
49
 
50
        // The 1st item in the array is the items count.
51
 
52
        $gbservice = new gradebookservices();
53
        $gbservice->set_type(lti_get_type($typeid));
54
        $this->create_graded_lti($typeid, $course, $resourceid, $tag);
55
        $gradeitems = $gbservice->get_lineitems($course->id, null, null, null, null, null, $typeid);
56
        $this->assertEquals(1, $gradeitems[0]);
57
        $lineitem = gradebookservices::item_for_json($gradeitems[1][0], '', $typeid);
58
        $this->assertFalse(isset($lineitem->submissionReview));
59
 
60
        $lineitemresource = new lineitem($gbservice);
61
 
62
        $this->set_server_for_put($course, $typeid, $lineitem);
63
 
64
        $response = new \mod_lti\local\ltiservice\response();
65
        $lineitem->resourceId = $resourceid.'modified';
66
        $lineitem->tag = $tag.'modified';
67
        $response->set_request_data(json_encode($lineitem));
68
 
69
        $lineitemresource->execute($response);
70
 
71
        $lineitem = gradebookservices::item_for_json($gradeitems[1][0], '', $typeid);
72
        $this->assertFalse(isset($lineitem->submissionReview));
73
        $this->assertEquals($resourceid.'modified', $lineitem->resourceId);
74
        $this->assertEquals($tag.'modified', $lineitem->tag);
75
        $responseitem = json_decode($response->get_body());
76
        $this->assertEquals($resourceid.'modified', $responseitem->resourceId);
77
    }
78
 
79
    /**
80
     * @covers ::execute
81
     *
82
     * Test updating the line item with submission review.
83
     */
11 efrain 84
    public function test_execute_put_withsubreview(): void {
1 efrain 85
        global $CFG;
86
        require_once($CFG->dirroot . '/mod/lti/locallib.php');
87
        $this->resetAfterTest();
88
        $this->setAdminUser();
89
        $resourceid = 'test-resource-id';
90
        $tag = 'tag';
91
        $subreviewurl = 'https://subreview.example.com';
92
        $subreviewparams = 'a=2';
93
        $course = $this->getDataGenerator()->create_course();
94
        $typeid = $this->create_type();
95
 
96
        // The 1st item in the array is the items count.
97
 
98
        $gbservice = new gradebookservices();
99
        $gbservice->set_type(lti_get_type($typeid));
100
        $this->create_graded_lti($typeid, $course, $resourceid, $tag, $subreviewurl, $subreviewparams);
101
        $gradeitems = $gbservice->get_lineitems($course->id, null, null, null, null, null, $typeid);
102
        $this->assertEquals(1, $gradeitems[0]);
103
        $lineitem = gradebookservices::item_for_json($gradeitems[1][0], '', $typeid);
104
        $this->assertTrue(isset($lineitem->submissionReview));
105
 
106
        $lineitemresource = new lineitem($gbservice);
107
 
108
        $this->set_server_for_put($course, $typeid, $lineitem);
109
 
110
        $response = new \mod_lti\local\ltiservice\response();
111
        $lineitem->resourceId = $resourceid.'modified';
112
        $lineitem->tag = $tag.'modified';
113
        $lineitem->submissionReview->url = $subreviewurl.'modified';
114
        $lineitem->submissionReview->custom = ['a' => '3'];
115
        $response->set_request_data(json_encode($lineitem));
116
 
117
        $lineitemresource->execute($response);
118
 
119
        $lineitem = gradebookservices::item_for_json($gradeitems[1][0], '', $typeid);
120
        $this->assertEquals($resourceid.'modified', $lineitem->resourceId);
121
        $this->assertEquals($subreviewurl.'modified', $lineitem->submissionReview->url);
122
        $custom = $lineitem->submissionReview->custom;
123
        $this->assertEquals('a=3', join("\n", array_map(fn($k) => $k.'='.$custom[$k], array_keys($custom))));
124
 
125
        $responseitem = json_decode($response->get_body());
126
        $this->assertEquals($resourceid.'modified', $responseitem->resourceId);
127
        $this->assertEquals($subreviewurl.'modified', $responseitem->submissionReview->url);
128
    }
129
 
130
    /**
131
     * @covers ::execute
132
     *
133
     * Test updating the line item with submission review.
134
     */
11 efrain 135
    public function test_execute_put_addsubreview(): void {
1 efrain 136
        global $CFG;
137
        require_once($CFG->dirroot . '/mod/lti/locallib.php');
138
        $this->resetAfterTest();
139
        $this->setAdminUser();
140
        $resourceid = 'test-resource-id';
141
        $tag = 'tag';
142
        $subreviewurl = 'https://subreview.example.com';
143
        $course = $this->getDataGenerator()->create_course();
144
        $typeid = $this->create_type();
145
 
146
        // The 1st item in the array is the items count.
147
 
148
        $gbservice = new gradebookservices();
149
        $gbservice->set_type(lti_get_type($typeid));
150
        $this->create_graded_lti($typeid, $course, $resourceid, $tag);
151
        $gradeitems = $gbservice->get_lineitems($course->id, null, null, null, null, null, $typeid);
152
        $this->assertEquals(1, $gradeitems[0]);
153
        $lineitem = gradebookservices::item_for_json($gradeitems[1][0], '', $typeid);
154
        $this->assertFalse(isset($lineitem->submissionReview));
155
 
156
        $lineitemresource = new lineitem($gbservice);
157
 
158
        $this->set_server_for_put($course, $typeid, $lineitem);
159
 
160
        $response = new \mod_lti\local\ltiservice\response();
161
        $lineitem->resourceId = $resourceid.'modified';
162
        $lineitem->tag = $tag.'modified';
163
        $lineitem->submissionReview = ['url' => $subreviewurl];
164
        $response->set_request_data(json_encode($lineitem));
165
 
166
        $lineitemresource->execute($response);
167
 
168
        $lineitem = gradebookservices::item_for_json($gradeitems[1][0], '', $typeid);
169
        $this->assertEquals($resourceid.'modified', $lineitem->resourceId);
170
        $this->assertEquals($subreviewurl, $lineitem->submissionReview->url);
171
        $this->assertFalse(isset($lineitem->submissionReview->custom));
172
 
173
        $responseitem = json_decode($response->get_body());
174
        $this->assertEquals($resourceid.'modified', $responseitem->resourceId);
175
        $this->assertEquals($subreviewurl, $responseitem->submissionReview->url);
176
        $this->assertFalse(isset($responseitem->submissionReview->custom));
177
    }
178
 
179
    /**
1441 ariadna 180
     * Test running a series of score updates, highlighting problems with the score posting logic.
181
     *
182
     * @covers ::execute
183
     *
184
     * @return void
185
     */
186
    public function test_sequential_score_posts(): void {
187
        global $CFG;
188
        require_once($CFG->dirroot . '/mod/lti/locallib.php');
189
        $this->resetAfterTest();
190
        $resourceid = 'test-resource-id';
191
        $tag = 'tag';
192
        $course = $this->getDataGenerator()->create_course();
193
        $typeid = $this->create_type();
194
        $user = $this->getDataGenerator()->create_and_enrol($course);
195
 
196
        // Create mod instance with line item - nothing pushed via services yet.
197
        $gbservice = new gradebookservices();
198
        $gbservice->set_type(lti_get_type($typeid));
199
        $modinstance = $this->create_graded_lti($typeid, $course, $resourceid, $tag);
200
        $gradeitems = $gbservice->get_lineitems($course->id, null, null, null, null, null, $typeid);
201
        $this->assertEquals(1, $gradeitems[0]); // The 1st item in the array is the items count.
202
 
203
        // Post a score so that there's at least one grade present at the time of lineitem update.
204
        $score = new scores($gbservice);
205
        $_SERVER['REQUEST_METHOD'] = \mod_lti\local\ltiservice\resource_base::HTTP_POST;
206
        $_SERVER['PATH_INFO'] = "/$course->id/lineitems/{$gradeitems[1][0]->id}/lineitem/scores?type_id=$typeid";
207
        $token = lti_new_access_token($typeid, ['https://purl.imsglobal.org/spec/lti-ags/scope/score']);
208
        $_SERVER['HTTP_Authorization'] = 'Bearer '.$token->token;
209
        $_GET['type_id'] = (string)$typeid;
210
        $requestdata = [
211
            'scoreGiven' => "8.0",
212
            'scoreMaximum' => "10.0",
213
            'activityProgress' => "Completed",
214
            'timestamp' => "2024-08-07T18:54:36.736+00:00",
215
            'gradingProgress' => "FullyGraded",
216
            'userId' => $user->id,
217
        ];
218
        $response = new \mod_lti\local\ltiservice\response();
219
        $response->set_content_type('application/vnd.ims.lis.v1.score+json');
220
        $response->set_request_data(json_encode($requestdata));
221
        $score->execute($response);
222
 
223
        // The grade in Moodle should reflect the score->timestamp, not the time of the score post.
224
        $grades = grade_get_grades($course->id, 'mod', 'lti', $modinstance->id, $user->id);
225
        $studentgrade = array_shift($grades->items[0]->grades);
226
        $this->assertEquals(strtotime($requestdata['timestamp']), $studentgrade->dategraded);
227
 
228
        // Read the results via the service. This should also return the correct dategraded time (timemodified in LTI terms).
229
        $_SERVER['REQUEST_METHOD'] = \mod_lti\local\ltiservice\resource_base::HTTP_GET;
230
        $_SERVER['PATH_INFO'] = "/$course->id/lineitems/{$gradeitems[1][0]->id}/lineitem/results?type_id=$typeid";
231
        $token = lti_new_access_token($typeid, ['https://purl.imsglobal.org/spec/lti-ags/scope/result.readonly']);
232
        $_SERVER['HTTP_Authorization'] = 'Bearer '.$token->token;
233
        $_GET['type_id'] = (string)$typeid;
234
        $result = new results($gbservice);
235
        $response = new \mod_lti\local\ltiservice\response();
236
        $response->set_content_type('application/vnd.ims.lis.v2.resultcontainer+json');
237
        $result->execute($response);
238
        $body = json_decode($response->get_body());
239
        $result = array_shift($body);
240
        $this->assertEquals(strtotime($requestdata['timestamp']), strtotime($result->timestamp));
241
 
242
        // Now, try to post a newer score using a timestamp that is greater than the one originally sent, but less than the time at
243
        // which the score was last posted. This should be valid since the timestamp is greater than the original posted score.
244
        $_SERVER['REQUEST_METHOD'] = \mod_lti\local\ltiservice\resource_base::HTTP_POST;
245
        $_SERVER['PATH_INFO'] = "/$course->id/lineitems/{$gradeitems[1][0]->id}/lineitem/scores?type_id=$typeid";
246
        $token = lti_new_access_token($typeid, ['https://purl.imsglobal.org/spec/lti-ags/scope/score']);
247
        $_SERVER['HTTP_Authorization'] = 'Bearer '.$token->token;
248
        $_GET['type_id'] = (string)$typeid;
249
        $requestdata['scoreGiven'] = "14";
250
        $requestdata['timestamp'] = "2024-08-08T18:54:36.736+00:00";
251
        $response = new \mod_lti\local\ltiservice\response();
252
        $response->set_content_type('application/vnd.ims.lis.v1.score+json');
253
        $response->set_request_data(json_encode($requestdata));
254
        $score->execute($response);
255
        $this->assertEquals(200, json_decode($response->get_code()));
256
 
257
        // Finally, post a score that's just been updated (i.e. score->timestamp = now).
258
        $_SERVER['REQUEST_METHOD'] = \mod_lti\local\ltiservice\resource_base::HTTP_POST;
259
        $_SERVER['PATH_INFO'] = "/$course->id/lineitems/{$gradeitems[1][0]->id}/lineitem/scores?type_id=$typeid";
260
        $token = lti_new_access_token($typeid, ['https://purl.imsglobal.org/spec/lti-ags/scope/score']);
261
        $_SERVER['HTTP_Authorization'] = 'Bearer '.$token->token;
262
        $_GET['type_id'] = (string)$typeid;
263
        $requestdata['scoreGiven'] = "15";
264
        $requestdata['timestamp'] = date('c', time());
265
        $response = new \mod_lti\local\ltiservice\response();
266
        $response->set_content_type('application/vnd.ims.lis.v1.score+json');
267
        $response->set_request_data(json_encode($requestdata));
268
        $score->execute($response);
269
        $this->assertEquals(200, json_decode($response->get_code()));
270
    }
271
 
272
    /**
1 efrain 273
     * Inserts a graded lti instance, which should create a grade_item and gradebookservices record.
274
     *
275
     * @param int $typeid Type ID of the LTI Tool.
276
     * @param object $course course where to add the lti instance.
277
     * @param string|null $resourceid resource id
278
     * @param string|null $tag tag
279
     * @param string|null $subreviewurl submission review url
280
     * @param string|null $subreviewparams submission review custom params
281
     *
282
     * @return object lti instance created
283
     */
284
    private function create_graded_lti(int $typeid, object $course, ?string $resourceid, ?string $tag,
285
            ?string $subreviewurl = null, ?string $subreviewparams = null): object {
286
 
287
        $lti = ['course' => $course->id,
288
            'typeid' => $typeid,
289
            'instructorchoiceacceptgrades' => LTI_SETTING_ALWAYS,
290
            'grade' => 10,
291
            'lineitemresourceid' => $resourceid,
292
            'lineitemtag' => $tag,
293
            'lineitemsubreviewurl' => $subreviewurl,
294
            'lineitemsubreviewparams' => $subreviewparams];
295
 
296
        return $this->getDataGenerator()->create_module('lti', $lti, array());
297
    }
298
 
299
    /**
300
     * Creates a new LTI Tool Type.
301
     */
302
    private function create_type() {
303
        $type = new \stdClass();
304
        $type->state = LTI_TOOL_STATE_CONFIGURED;
305
        $type->name = "Test tool";
306
        $type->description = "Example description";
307
        $type->clientid = "Test client ID";
308
        $type->baseurl = $this->getExternalTestFileUrl('/test.html');
309
 
310
        $config = new \stdClass();
311
        $config->ltiservice_gradesynchronization = 2;
312
        return lti_add_type($type, $config);
313
    }
314
 
315
    /**
316
     * Sets the server info and get to be configured for a PUT operation,
317
     * including having a proper auth token attached.
318
     *
319
     * @param object $course course where to add the lti instance.
320
     * @param int $typeid
321
     * @param object $lineitem
322
     */
323
    private function set_server_for_put(object $course, int $typeid, object $lineitem) {
324
        $_SERVER['REQUEST_METHOD'] = \mod_lti\local\ltiservice\resource_base::HTTP_PUT;
325
        $_SERVER['PATH_INFO'] = "/$course->id/lineitems$lineitem->id";
326
 
327
        $token = lti_new_access_token($typeid, ['https://purl.imsglobal.org/spec/lti-ags/scope/lineitem']);
328
        $_SERVER['HTTP_Authorization'] = 'Bearer '.$token->token;
329
        $_GET['type_id'] = (string)$typeid;
330
    }
331
}