Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1441 ariadna 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 core_ai\provider;
18
 
19
use core_ai\aiactions\generate_image;
20
use core_ai\aiactions\generate_text;
21
use core_ai\aiactions\responses\response_generate_image;
22
use core_ai\aiactions\responses\response_generate_text;
23
use core_ai\aiactions\responses\response_summarise_text;
24
use core_ai\aiactions\summarise_text;
25
use core_ai\manager;
26
use core_ai\privacy\provider;
27
use core_privacy\local\request\approved_contextlist;
28
use core_privacy\local\request\approved_userlist;
29
use core_privacy\local\request\writer;
30
use core_privacy\tests\request\content_writer;
31
 
32
/**
33
 * Unit tests for \core_ai\provider
34
 *
35
 * @package    core_ai
36
 * @copyright  2024 Matt Porritt <matt.porritt@moodle.com>
37
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
38
 * @covers     \core_ai\provider
39
 */
40
final class provider_test extends \advanced_testcase {
41
 
42
    /** @var \core_ai\manager */
43
    private $manager;
44
 
45
    /** @var \core_ai\provider */
46
    private $provider;
47
 
48
    /**
49
     * Overriding setUp() function to always reset after tests.
50
     */
51
    public function setUp(): void {
52
        parent::setUp();
53
        $this->resetAfterTest();
54
 
55
        // Create the provider instance.
56
        $this->manager = \core\di::get(\core_ai\manager::class);
57
        $config = ['data' => 'goeshere'];
58
        $this->provider = $this->manager->create_provider_instance(
59
                classname: '\aiprovider_openai\provider',
60
                name: 'dummy',
61
                config: $config,
62
        );
63
    }
64
 
65
    /**
66
     * Test get_contexts_for_userid().
67
     */
68
    public function test_get_contexts_for_userid(): void {
69
        $user1 = $this->getDataGenerator()->create_user();
70
        $user2 = $this->getDataGenerator()->create_user();
71
        $course1 = $this->getDataGenerator()->create_course();
72
        $course2 = $this->getDataGenerator()->create_course();
73
        $course1context = \context_course::instance($course1->id);
74
        $course2context = \context_course::instance($course2->id);
75
        $syscontext = \context_system::instance();
76
 
77
        // AI policy.
78
        // Set the user policy.
79
        manager::user_policy_accepted($user1->id, $course1context->id);
80
 
81
        // Retrieve the user1's context ids.
82
        $contextids = provider::get_contexts_for_userid($user1->id);
83
        $this->assertNotEmpty($contextids->get_contextids());
84
        $this->assertCount(1, $contextids->get_contextids());
85
        $this->assertTrue(in_array($course1context->id, $contextids->get_contextids()));
86
        // Retrieve the user2's context ids.
87
        $contextids = provider::get_contexts_for_userid($user2->id);
88
        $this->assertEmpty($contextids->get_contextids());
89
 
90
        // AI generate text.
91
        $action = new generate_text(
92
            contextid: $course2context->id,
93
            userid: $user1->id,
94
            prompttext: 'This is a test prompt',
95
        );
96
 
97
        $body = [
98
            'id' => 'chatcmpl-123',
99
            'fingerprint' => 'fp_44709d6fcb',
100
            'generatedcontent' => 'This is the generated content',
101
            'finishreason' => 'stop',
102
            'prompttokens' => 9,
103
            'completiontokens' => 12,
104
            'model' => 'gpt-4o',
105
        ];
106
        $actionresponse = new response_generate_text(
107
            success: true,
108
        );
109
        $actionresponse->set_response_data($body);
110
 
111
        $method = new \ReflectionMethod($this->manager, 'store_action_result');
112
        $method->invoke($this->manager, $this->provider, $action, $actionresponse);
113
 
114
        // Retrieve the user1's context ids.
115
        $contextids = provider::get_contexts_for_userid($user1->id);
116
        $this->assertNotEmpty($contextids->get_contextids());
117
        $this->assertCount(2, $contextids->get_contextids());
118
        $this->assertTrue(in_array($course1context->id, $contextids->get_contextids()));
119
        $this->assertTrue(in_array($course2context->id, $contextids->get_contextids()));
120
 
121
        // AI generate image.
122
        $action = new generate_image(
123
            contextid: $syscontext->id,
124
            userid: $user2->id,
125
            prompttext: 'This is a test prompt',
126
            quality: 'hd',
127
            aspectratio: 'square',
128
            numimages: 1,
129
            style: 'vivid',
130
        );
131
 
132
        $body = [
133
            'revisedprompt' => 'This is a revised prompt',
134
            'sourceurl' => 'https://example.com/image.png',
135
            'model' => 'dall-e-3',
136
        ];
137
        $actionresponse = new response_generate_image(
138
            success: true,
139
        );
140
        $actionresponse->set_response_data($body);
141
 
142
        $method = new \ReflectionMethod($this->manager, 'store_action_result');
143
        $method->invoke($this->manager, $this->provider, $action, $actionresponse);
144
 
145
        // Retrieve the user1's context ids.
146
        $contextids = provider::get_contexts_for_userid($user1->id);
147
        $this->assertNotEmpty($contextids->get_contextids());
148
        $this->assertCount(2, $contextids->get_contextids());
149
        $this->assertTrue(in_array($course1context->id, $contextids->get_contextids()));
150
        $this->assertTrue(in_array($course2context->id, $contextids->get_contextids()));
151
 
152
        // Retrieve the user2's context ids.
153
        $contextids = provider::get_contexts_for_userid($user2->id);
154
        $this->assertNotEmpty($contextids->get_contextids());
155
        $this->assertCount(1, $contextids->get_contextids());
156
        $this->assertFalse(in_array($course2context->id, $contextids->get_contextids()));
157
        $this->assertTrue(in_array($syscontext->id, $contextids->get_contextids()));
158
 
159
        // AI summarise text.
160
        $action = new summarise_text(
161
            contextid: $course2context->id,
162
            userid: $user2->id,
163
            prompttext: 'This is a test prompt',
164
        );
165
 
166
        $body = [
167
            'id' => 'chatcmpl-123',
168
            'fingerprint' => 'fp_44709d6fcb',
169
            'generatedcontent' => 'This is the generated content',
170
            'finishreason' => 'stop',
171
            'prompttokens' => 9,
172
            'completiontokens' => 12,
173
            'model' => 'gpt-4o',
174
        ];
175
        $actionresponse = new response_generate_text(
176
            success: true,
177
        );
178
        $actionresponse->set_response_data($body);
179
 
180
        $method = new \ReflectionMethod($this->manager, 'store_action_result');
181
        $method->invoke($this->manager, $this->provider, $action, $actionresponse);
182
 
183
        // Retrieve the user2's context ids.
184
        $contextids = provider::get_contexts_for_userid($user2->id);
185
        $this->assertNotEmpty($contextids->get_contextids());
186
        $this->assertCount(2, $contextids->get_contextids());
187
        $this->assertTrue(in_array($course2context->id, $contextids->get_contextids()));
188
        $this->assertTrue(in_array($syscontext->id, $contextids->get_contextids()));
189
    }
190
 
191
    /**
192
     * Test export_user_data() for AI policy.
193
     */
194
    public function test_export_user_data_for_policy(): void {
195
        $user = $this->getDataGenerator()->create_user();
196
        $course = $this->getDataGenerator()->create_course();
197
        $coursecontext = \context_course::instance($course->id);
198
 
199
        // Set the user policy.
200
        manager::user_policy_accepted($user->id, $coursecontext->id);
201
 
202
        // Retrieve the user's context ids.
203
        $contextlist = provider::get_contexts_for_userid($user->id);
204
        $approvedcontextlist = new approved_contextlist($user, 'core_ai', $contextlist->get_contextids());
205
        provider::export_user_data($approvedcontextlist);
206
 
207
        foreach ($contextlist as $context) {
208
            /** @var content_writer $writer */
209
            $writer = writer::with_context($context);
210
            $this->assertTrue($writer->has_any_data());
211
            $subcontexts = [
212
                get_string('ai', 'core_ai'),
213
            ];
214
            $name = 'policy';
215
            $data = $writer->get_related_data($subcontexts, $name);
216
            $this->assertEquals($coursecontext->id, $data->contextid);
217
        }
218
    }
219
 
220
    /**
221
     * Test export_user_data() for generate text.
222
     */
223
    public function test_export_user_data_for_generate_text(): void {
224
        $user = $this->getDataGenerator()->create_user();
225
        $course1 = $this->getDataGenerator()->create_course();
226
        $course2 = $this->getDataGenerator()->create_course();
227
        $course1context = \context_course::instance($course1->id);
228
        $course2context = \context_course::instance($course2->id);
229
        $clock = $this->mock_clock_with_frozen();
230
 
231
        $timecreated1 = $clock->time();
232
        $action = new generate_text(
233
            contextid: $course1context->id,
234
            userid: $user->id,
235
            prompttext: 'This is a test prompt 1',
236
        );
237
 
238
        $body = [
239
            'id' => 'chatcmpl-123',
240
            'fingerprint' => 'fp_44709d6fcb',
241
            'generatedcontent' => 'This is the generated content 1',
242
            'finishreason' => 'stop',
243
            'prompttokens' => 9,
244
            'completiontokens' => 12,
245
            'model' => 'gpt-4o',
246
        ];
247
        $actionresponse = new response_generate_text(
248
            success: true,
249
        );
250
        $actionresponse->set_response_data($body);
251
 
252
        $method = new \ReflectionMethod($this->manager, 'store_action_result');
253
        $method->invoke($this->manager, $this->provider, $action, $actionresponse);
254
 
255
        $clock->bump(2);
256
        $timecreated2 = $clock->time();
257
        $action = new generate_text(
258
            contextid: $course2context->id,
259
            userid: $user->id,
260
            prompttext: 'This is a test prompt 2',
261
        );
262
 
263
        $body = [
264
            'id' => 'chatcmpl-123',
265
            'fingerprint' => 'fp_44709d6fcb',
266
            'generatedcontent' => 'This is the generated content 2',
267
            'finishreason' => 'stop',
268
            'prompttokens' => 10,
269
            'completiontokens' => 15,
270
            'model' => 'gpt-4o',
271
        ];
272
        $actionresponse = new response_generate_text(
273
            success: true,
274
        );
275
        $actionresponse->set_response_data($body);
276
        $method->invoke($this->manager, $this->provider, $action, $actionresponse);
277
 
278
        // Retrieve the user's context ids.
279
        $contextlist = provider::get_contexts_for_userid($user->id);
280
        $approvedcontextlist = new approved_contextlist($user, 'core_ai', $contextlist->get_contextids());
281
        provider::export_user_data($approvedcontextlist);
282
 
283
        foreach ($contextlist as $context) {
284
            /** @var content_writer $writer */
285
            $writer = writer::with_context($context);
286
            $this->assertTrue($writer->has_any_data());
287
 
288
            if ($context->instanceid == $course1context->instanceid) {
289
                $subcontexts = [
290
                    get_string('ai', 'core_ai'),
291
                    get_string('action_generate_text', 'core_ai'),
292
                    date('c', $timecreated1),
293
                ];
294
                $name = 'action_generate_text';
295
                $data = $writer->get_related_data($subcontexts, $name);
296
                $this->assertEquals('generate_text', $data->actionname);
297
                $this->assertEquals($course1context->id, $data->contextid);
298
                $this->assertEquals('This is a test prompt 1', $data->prompt);
299
                $this->assertEquals('This is the generated content 1', $data->generatedcontent);
300
                $this->assertEquals('9', $data->prompttokens);
301
                $this->assertEquals('12', $data->completiontoken);
302
                $this->assertEquals(get_string('yes'), $data->success);
303
                $this->assertEquals('gpt-4o', $data->model);
304
            }
305
 
306
            if ($context->instanceid == $course2context->instanceid) {
307
                $subcontexts = [
308
                    get_string('ai', 'core_ai'),
309
                    get_string('action_generate_text', 'core_ai'),
310
                    date('c', $timecreated2),
311
                ];
312
                $name = 'action_generate_text';
313
                $data = $writer->get_related_data($subcontexts, $name);
314
                $this->assertEquals('generate_text', $data->actionname);
315
                $this->assertEquals($course2context->id, $data->contextid);
316
                $this->assertEquals('This is a test prompt 2', $data->prompt);
317
                $this->assertEquals('This is the generated content 2', $data->generatedcontent);
318
                $this->assertEquals('10', $data->prompttokens);
319
                $this->assertEquals('15', $data->completiontoken);
320
                $this->assertEquals(get_string('yes'), $data->success);
321
                $this->assertEquals('gpt-4o', $data->model);
322
            }
323
        }
324
    }
325
 
326
    /**
327
     * Test export_user_data() for generate image.
328
     */
329
    public function test_export_user_data_for_generate_image(): void {
330
        $user = $this->getDataGenerator()->create_user();
331
        $course1 = $this->getDataGenerator()->create_course();
332
        $course2 = $this->getDataGenerator()->create_course();
333
        $course1context = \context_course::instance($course1->id);
334
        $course2context = \context_course::instance($course2->id);
335
        $clock = $this->mock_clock_with_frozen();
336
 
337
        $timecreated1 = $clock->time();
338
        $action = new generate_image(
339
            contextid: $course1context->id,
340
            userid: $user->id,
341
            prompttext: 'This is a test prompt 1',
342
            quality: 'hd',
343
            aspectratio: 'square',
344
            numimages: 1,
345
            style: 'vivid',
346
        );
347
 
348
        $body = [
349
            'revisedprompt' => 'This is a revised prompt',
350
            'sourceurl' => 'https://example.com/image1.png',
351
            'model' => 'dall-e-3',
352
        ];
353
        $actionresponse = new response_generate_image(
354
            success: true,
355
        );
356
        $actionresponse->set_response_data($body);
357
 
358
        $method = new \ReflectionMethod($this->manager, 'store_action_result');
359
        $method->invoke($this->manager, $this->provider, $action, $actionresponse);
360
 
361
        $clock->bump(2);
362
        $timecreated2 = $clock->time();
363
        $action = new generate_image(
364
            contextid: $course2context->id,
365
            userid: $user->id,
366
            prompttext: 'This is a test prompt 2',
367
            quality: 'hd',
368
            aspectratio: 'portrait',
369
            numimages: 2,
370
            style: 'vivid',
371
        );
372
 
373
        $body = [
374
            'revisedprompt' => 'This is a revised prompt',
375
            'sourceurl' => 'https://example.com/image2.png',
376
            'model' => 'dall-e-3',
377
        ];
378
        $actionresponse = new response_generate_image(
379
            success: true,
380
        );
381
        $actionresponse->set_response_data($body);
382
        $method->invoke($this->manager, $this->provider, $action, $actionresponse);
383
 
384
        // Retrieve the user's context ids.
385
        $contextlist = provider::get_contexts_for_userid($user->id);
386
        $approvedcontextlist = new approved_contextlist($user, 'core_ai', $contextlist->get_contextids());
387
        provider::export_user_data($approvedcontextlist);
388
 
389
        foreach ($contextlist as $context) {
390
            /** @var content_writer $writer */
391
            $writer = writer::with_context($context);
392
            $this->assertTrue($writer->has_any_data());
393
 
394
            if ($context->instanceid == $course1context->instanceid) {
395
                $subcontexts = [
396
                    get_string('ai', 'core_ai'),
397
                    get_string('action_generate_image', 'core_ai'),
398
                    date('c', $timecreated1),
399
                ];
400
                $name = 'action_generate_image';
401
                $data = $writer->get_related_data($subcontexts, $name);
402
                $this->assertEquals('generate_image', $data->actionname);
403
                $this->assertEquals($course1context->id, $data->contextid);
404
                $this->assertEquals('This is a test prompt 1', $data->prompt);
405
                $this->assertEquals('1', $data->numberimages);
406
                $this->assertEquals('hd', $data->quality);
407
                $this->assertEquals('square', $data->aspectratio);
408
                $this->assertEquals('vivid', $data->style);
409
                $this->assertEquals('https://example.com/image1.png', $data->sourceurl);
410
                $this->assertEquals(get_string('yes'), $data->success);
411
                $this->assertEquals('dall-e-3', $data->model);
412
            }
413
 
414
            if ($context->instanceid == $course2context->instanceid) {
415
                $subcontexts = [
416
                    get_string('ai', 'core_ai'),
417
                    get_string('action_generate_image', 'core_ai'),
418
                    date('c', $timecreated2),
419
                ];
420
                $name = 'action_generate_image';
421
                $data = $writer->get_related_data($subcontexts, $name);
422
                $this->assertEquals('generate_image', $data->actionname);
423
                $this->assertEquals($course2context->id, $data->contextid);
424
                $this->assertEquals('This is a test prompt 2', $data->prompt);
425
                $this->assertEquals('2', $data->numberimages);
426
                $this->assertEquals('hd', $data->quality);
427
                $this->assertEquals('portrait', $data->aspectratio);
428
                $this->assertEquals('vivid', $data->style);
429
                $this->assertEquals('https://example.com/image2.png', $data->sourceurl);
430
                $this->assertEquals(get_string('yes'), $data->success);
431
                $this->assertEquals('dall-e-3', $data->model);
432
            }
433
        }
434
    }
435
 
436
    /**
437
     * Test export_user_data() for summarise text.
438
     */
439
    public function test_export_user_data_for_summarise_text(): void {
440
        $user = $this->getDataGenerator()->create_user();
441
        $course1 = $this->getDataGenerator()->create_course();
442
        $course2 = $this->getDataGenerator()->create_course();
443
        $course1context = \context_course::instance($course1->id);
444
        $course2context = \context_course::instance($course2->id);
445
        $clock = $this->mock_clock_with_frozen();
446
 
447
        $timecreated1 = $clock->time();
448
        $action = new summarise_text(
449
            contextid: $course1context->id,
450
            userid: $user->id,
451
            prompttext: 'This is a test prompt 1',
452
        );
453
 
454
        $body = [
455
            'id' => 'chatcmpl-123',
456
            'fingerprint' => 'fp_44709d6fcb',
457
            'generatedcontent' => 'This is the generated content 1',
458
            'finishreason' => 'stop',
459
            'prompttokens' => 9,
460
            'completiontokens' => 12,
461
            'model' => 'gpt-4o',
462
        ];
463
        $actionresponse = new response_summarise_text(
464
            success: true,
465
        );
466
        $actionresponse->set_response_data($body);
467
 
468
        $method = new \ReflectionMethod($this->manager, 'store_action_result');
469
        $method->invoke($this->manager, $this->provider, $action, $actionresponse);
470
 
471
        $clock->bump(2);
472
        $timecreated2 = $clock->time();
473
        $action = new summarise_text(
474
            contextid: $course2context->id,
475
            userid: $user->id,
476
            prompttext: 'This is a test prompt 2',
477
        );
478
 
479
        $body = [
480
            'id' => 'chatcmpl-123',
481
            'fingerprint' => 'fp_44709d6fcb',
482
            'generatedcontent' => 'This is the generated content 2',
483
            'finishreason' => 'stop',
484
            'prompttokens' => 10,
485
            'completiontokens' => 15,
486
            'model' => 'gpt-4o',
487
        ];
488
        $actionresponse = new response_summarise_text(
489
            success: true,
490
        );
491
        $actionresponse->set_response_data($body);
492
        $method->invoke($this->manager, $this->provider, $action, $actionresponse);
493
 
494
        // Retrieve the user's context ids.
495
        $contextlist = provider::get_contexts_for_userid($user->id);
496
        $approvedcontextlist = new approved_contextlist($user, 'core_ai', $contextlist->get_contextids());
497
        provider::export_user_data($approvedcontextlist);
498
 
499
        foreach ($contextlist as $context) {
500
            /** @var content_writer $writer */
501
            $writer = writer::with_context($context);
502
            $this->assertTrue($writer->has_any_data());
503
 
504
            if ($context->instanceid == $course1context->instanceid) {
505
                $subcontexts = [
506
                    get_string('ai', 'core_ai'),
507
                    get_string('action_summarise_text', 'core_ai'),
508
                    date('c', $timecreated1),
509
                ];
510
                $name = 'action_summarise_text';
511
                $data = $writer->get_related_data($subcontexts, $name);
512
                $this->assertEquals('summarise_text', $data->actionname);
513
                $this->assertEquals($course1context->id, $data->contextid);
514
                $this->assertEquals('This is a test prompt 1', $data->prompt);
515
                $this->assertEquals('This is the generated content 1', $data->generatedcontent);
516
                $this->assertEquals('9', $data->prompttokens);
517
                $this->assertEquals('12', $data->completiontoken);
518
                $this->assertEquals(get_string('yes'), $data->success);
519
                $this->assertEquals('gpt-4o', $data->model);
520
            }
521
 
522
            if ($context->instanceid == $course2context->instanceid) {
523
                $subcontexts = [
524
                    get_string('ai', 'core_ai'),
525
                    get_string('action_summarise_text', 'core_ai'),
526
                    date('c', $timecreated2),
527
                ];
528
                $name = 'action_summarise_text';
529
                $data = $writer->get_related_data($subcontexts, $name);
530
                $this->assertEquals('summarise_text', $data->actionname);
531
                $this->assertEquals($course2context->id, $data->contextid);
532
                $this->assertEquals('This is a test prompt 2', $data->prompt);
533
                $this->assertEquals('This is the generated content 2', $data->generatedcontent);
534
                $this->assertEquals('10', $data->prompttokens);
535
                $this->assertEquals('15', $data->completiontoken);
536
                $this->assertEquals(get_string('yes'), $data->success);
537
                $this->assertEquals('gpt-4o', $data->model);
538
            }
539
        }
540
    }
541
 
542
    /**
543
     * Test delete_data_for_all_users_in_context() for AI policy.
544
     */
545
    public function test_delete_data_for_all_users_in_context_for_policy(): void {
546
        global $DB;
547
        $user1 = $this->getDataGenerator()->create_user();
548
        $user2 = $this->getDataGenerator()->create_user();
549
        $user3 = $this->getDataGenerator()->create_user();
550
        $course1 = $this->getDataGenerator()->create_course();
551
        $course2 = $this->getDataGenerator()->create_course();
552
        $course1context = \context_course::instance($course1->id);
553
        $course2context = \context_course::instance($course2->id);
554
 
555
        // Set the user policy.
556
        manager::user_policy_accepted($user1->id, $course1context->id);
557
        manager::user_policy_accepted($user2->id, $course1context->id);
558
        manager::user_policy_accepted($user3->id, $course2context->id);
559
 
560
        provider::delete_data_for_all_users_in_context($course1context);
561
 
562
        // Verify all policy data for Course 1 has been deleted.
563
        $datas = $DB->get_records('ai_policy_register', ['contextid' => $course1context->id]);
564
        $this->assertCount(0, $datas);
565
 
566
        // Verify policy data for Course 2 are still present.
567
        $datas = $DB->get_records('ai_policy_register', ['contextid' => $course2context->id]);
568
        $this->assertCount(1, $datas);
569
    }
570
 
571
    /**
572
     * Test delete_data_for_all_users_in_context() for generate text.
573
     */
574
    public function test_delete_data_for_all_users_in_context_for_generate_text(): void {
575
        global $DB;
576
        $user = $this->getDataGenerator()->create_user();
577
        $course1 = $this->getDataGenerator()->create_course();
578
        $course2 = $this->getDataGenerator()->create_course();
579
        $course1context = \context_course::instance($course1->id);
580
        $course2context = \context_course::instance($course2->id);
581
        $clock = $this->mock_clock_with_frozen();
582
 
583
        $action = new generate_text(
584
            contextid: $course1context->id,
585
            userid: $user->id,
586
            prompttext: 'This is a test prompt 1',
587
        );
588
 
589
        $body = [
590
            'id' => 'chatcmpl-123',
591
            'fingerprint' => 'fp_44709d6fcb',
592
            'generatedcontent' => 'This is the generated content 1',
593
            'finishreason' => 'stop',
594
            'prompttokens' => 9,
595
            'completiontokens' => 12,
596
            'model' => 'gpt-4o',
597
        ];
598
        $actionresponse = new response_generate_text(
599
            success: true,
600
        );
601
        $actionresponse->set_response_data($body);
602
 
603
        $method = new \ReflectionMethod($this->manager, 'store_action_result');
604
        $storeresult1 = $method->invoke($this->manager, $this->provider, $action, $actionresponse);
605
 
606
        $clock->bump(2);
607
        $action = new generate_text(
608
            contextid: $course2context->id,
609
            userid: $user->id,
610
            prompttext: 'This is a test prompt 2',
611
        );
612
 
613
        $body = [
614
            'id' => 'chatcmpl-123',
615
            'fingerprint' => 'fp_44709d6fcb',
616
            'generatedcontent' => 'This is the generated content 2',
617
            'finishreason' => 'stop',
618
            'prompttokens' => 10,
619
            'completiontokens' => 15,
620
            'model' => 'gpt-4o',
621
        ];
622
        $actionresponse = new response_generate_text(
623
            success: true,
624
        );
625
        $actionresponse->set_response_data($body);
626
        $method = new \ReflectionMethod($this->manager, 'store_action_result');
627
        $storeresult2 = $method->invoke($this->manager, $this->provider, $action, $actionresponse);
628
 
629
        provider::delete_data_for_all_users_in_context($course1context);
630
 
631
        $actionid1 = $DB->get_field('ai_action_register', 'actionid', ['id' => $storeresult1]);
632
        $actionid2 = $DB->get_field('ai_action_register', 'actionid', ['id' => $storeresult2]);
633
 
634
        $record = $DB->get_record('ai_action_generate_text', ['id' => $actionid1]);
635
        $this->assertEquals('', $record->prompt);
636
        $this->assertEquals('', $record->responseid);
637
        $this->assertEquals('', $record->fingerprint);
638
        $this->assertEquals('', $record->generatedcontent);
639
 
640
        $record = $DB->get_record('ai_action_generate_text', ['id' => $actionid2]);
641
        $this->assertNotEquals('', $record->prompt);
642
        $this->assertNotEquals('', $record->responseid);
643
        $this->assertNotEquals('', $record->fingerprint);
644
        $this->assertNotEquals('', $record->generatedcontent);
645
    }
646
 
647
    /**
648
     * Test delete_data_for_all_users_in_context() for generate image.
649
     */
650
    public function test_delete_data_for_all_users_in_context_for_generate_image(): void {
651
        global $DB;
652
        $user = $this->getDataGenerator()->create_user();
653
        $course1 = $this->getDataGenerator()->create_course();
654
        $course2 = $this->getDataGenerator()->create_course();
655
        $course1context = \context_course::instance($course1->id);
656
        $course2context = \context_course::instance($course2->id);
657
        $clock = $this->mock_clock_with_frozen();
658
 
659
        $action = new generate_image(
660
            contextid: $course1context->id,
661
            userid: $user->id,
662
            prompttext: 'This is a test prompt 1',
663
            quality: 'hd',
664
            aspectratio: 'square',
665
            numimages: 1,
666
            style: 'vivid',
667
        );
668
 
669
        $body = [
670
            'revisedprompt' => 'This is a revised prompt',
671
            'sourceurl' => 'https://example.com/image1.png',
672
            'model' => 'dall-e-3',
673
        ];
674
        $actionresponse = new response_generate_image(
675
            success: true,
676
        );
677
        $actionresponse->set_response_data($body);
678
 
679
        $method = new \ReflectionMethod($this->manager, 'store_action_result');
680
        $storeresult1 = $method->invoke($this->manager, $this->provider, $action, $actionresponse);
681
 
682
        $clock->bump(2);
683
        $action = new generate_image(
684
            contextid: $course2context->id,
685
            userid: $user->id,
686
            prompttext: 'This is a test prompt 2',
687
            quality: 'hd',
688
            aspectratio: 'square',
689
            numimages: 1,
690
            style: 'vivid',
691
        );
692
 
693
        $body = [
694
            'revisedprompt' => 'This is a revised prompt',
695
            'sourceurl' => 'https://example.com/image2.png',
696
            'model' => 'dall-e-3',
697
        ];
698
        $actionresponse = new response_generate_image(
699
            success: true,
700
        );
701
        $actionresponse->set_response_data($body);
702
        $method = new \ReflectionMethod($this->manager, 'store_action_result');
703
        $storeresult2 = $method->invoke($this->manager, $this->provider, $action, $actionresponse);
704
 
705
        provider::delete_data_for_all_users_in_context($course1context);
706
 
707
        $actionid1 = $DB->get_field('ai_action_register', 'actionid', ['id' => $storeresult1]);
708
        $actionid2 = $DB->get_field('ai_action_register', 'actionid', ['id' => $storeresult2]);
709
 
710
        $record = $DB->get_record('ai_action_generate_image', ['id' => $actionid1]);
711
        $this->assertEquals('', $record->prompt);
712
        $this->assertEquals('', $record->sourceurl);
713
        $this->assertEquals('', $record->revisedprompt);
714
 
715
        $record = $DB->get_record('ai_action_generate_image', ['id' => $actionid2]);
716
        $this->assertNotEquals('', $record->prompt);
717
        $this->assertNotEquals('', $record->sourceurl);
718
        $this->assertNotEquals('', $record->revisedprompt);
719
    }
720
 
721
    /**
722
     * Test delete_data_for_all_users_in_context() for summarise text.
723
     */
724
    public function test_delete_data_for_all_users_in_context_for_summarise_text(): void {
725
        global $DB;
726
        $user = $this->getDataGenerator()->create_user();
727
        $course1 = $this->getDataGenerator()->create_course();
728
        $course2 = $this->getDataGenerator()->create_course();
729
        $course1context = \context_course::instance($course1->id);
730
        $course2context = \context_course::instance($course2->id);
731
        $clock = $this->mock_clock_with_frozen();
732
 
733
        $action = new summarise_text(
734
            contextid: $course1context->id,
735
            userid: $user->id,
736
            prompttext: 'This is a test prompt 1',
737
        );
738
 
739
        $body = [
740
            'id' => 'chatcmpl-123',
741
            'fingerprint' => 'fp_44709d6fcb',
742
            'generatedcontent' => 'This is the generated content 1',
743
            'finishreason' => 'stop',
744
            'prompttokens' => 9,
745
            'completiontokens' => 12,
746
            'model' => 'gpt-4o',
747
        ];
748
        $actionresponse = new response_summarise_text(
749
            success: true,
750
        );
751
        $actionresponse->set_response_data($body);
752
 
753
        $method = new \ReflectionMethod($this->manager, 'store_action_result');
754
        $storeresult1 = $method->invoke($this->manager, $this->provider, $action, $actionresponse);
755
 
756
        $clock->bump(2);
757
        $action = new summarise_text(
758
            contextid: $course2context->id,
759
            userid: $user->id,
760
            prompttext: 'This is a test prompt 2',
761
        );
762
 
763
        $body = [
764
            'id' => 'chatcmpl-123',
765
            'fingerprint' => 'fp_44709d6fcb',
766
            'generatedcontent' => 'This is the generated content 2',
767
            'finishreason' => 'stop',
768
            'prompttokens' => 10,
769
            'completiontokens' => 15,
770
            'model' => 'gpt-4o',
771
        ];
772
        $actionresponse = new response_summarise_text(
773
            success: true,
774
        );
775
        $actionresponse->set_response_data($body);
776
        $method = new \ReflectionMethod($this->manager, 'store_action_result');
777
        $storeresult2 = $method->invoke($this->manager, $this->provider, $action, $actionresponse);
778
 
779
        provider::delete_data_for_all_users_in_context($course1context);
780
 
781
        $actionid1 = $DB->get_field('ai_action_register', 'actionid', ['id' => $storeresult1]);
782
        $actionid2 = $DB->get_field('ai_action_register', 'actionid', ['id' => $storeresult2]);
783
 
784
        $record = $DB->get_record('ai_action_summarise_text', ['id' => $actionid1]);
785
        $this->assertEquals('', $record->prompt);
786
        $this->assertEquals('', $record->responseid);
787
        $this->assertEquals('', $record->fingerprint);
788
        $this->assertEquals('', $record->generatedcontent);
789
 
790
        $record = $DB->get_record('ai_action_summarise_text', ['id' => $actionid2]);
791
        $this->assertNotEquals('', $record->prompt);
792
        $this->assertNotEquals('', $record->responseid);
793
        $this->assertNotEquals('', $record->fingerprint);
794
        $this->assertNotEquals('', $record->generatedcontent);
795
    }
796
 
797
    /**
798
     * Test delete_data_for_all_users_in_context() for generate text.
799
     */
800
    public function test_delete_data_for_user_for_generate_text(): void {
801
        global $DB;
802
        $user1 = $this->getDataGenerator()->create_user();
803
        $user2 = $this->getDataGenerator()->create_user();
804
        $course = $this->getDataGenerator()->create_course();
805
        $coursecontext = \context_course::instance($course->id);
806
        $clock = $this->mock_clock_with_frozen();
807
 
808
        $action = new generate_text(
809
            contextid: $coursecontext->id,
810
            userid: $user1->id,
811
            prompttext: 'This is a test prompt 1',
812
        );
813
 
814
        $body = [
815
            'id' => 'chatcmpl-123',
816
            'fingerprint' => 'fp_44709d6fcb',
817
            'generatedcontent' => 'This is the generated content 1',
818
            'finishreason' => 'stop',
819
            'prompttokens' => 9,
820
            'completiontokens' => 12,
821
            'model' => 'gpt-4o',
822
        ];
823
        $actionresponse = new response_generate_text(
824
            success: true,
825
        );
826
        $actionresponse->set_response_data($body);
827
 
828
        $method = new \ReflectionMethod($this->manager, 'store_action_result');
829
        $storeresult1 = $method->invoke($this->manager, $this->provider, $action, $actionresponse);
830
 
831
        $clock->bump(2);
832
        $action = new generate_text(
833
            contextid: $coursecontext->id,
834
            userid: $user2->id,
835
            prompttext: 'This is a test prompt 2',
836
        );
837
 
838
        $body = [
839
            'id' => 'chatcmpl-123',
840
            'fingerprint' => 'fp_44709d6fcb',
841
            'generatedcontent' => 'This is the generated content 2',
842
            'finishreason' => 'stop',
843
            'prompttokens' => 10,
844
            'completiontokens' => 15,
845
            'model' => 'gpt-4o',
846
        ];
847
        $actionresponse = new response_generate_text(
848
            success: true,
849
        );
850
        $actionresponse->set_response_data($body);
851
        $method = new \ReflectionMethod($this->manager, 'store_action_result');
852
        $storeresult2 = $method->invoke($this->manager, $this->provider, $action, $actionresponse);
853
 
854
        $contextlist = provider::get_contexts_for_userid($user1->id);
855
        $approvedcontextlist = new approved_contextlist($user1, 'core_ai', $contextlist->get_contextids());
856
        provider::delete_data_for_user($approvedcontextlist);
857
 
858
        $actionid1 = $DB->get_field('ai_action_register', 'actionid', ['id' => $storeresult1]);
859
        $actionid2 = $DB->get_field('ai_action_register', 'actionid', ['id' => $storeresult2]);
860
 
861
        $record = $DB->get_record('ai_action_generate_text', ['id' => $actionid1]);
862
        $this->assertEquals('', $record->prompt);
863
        $this->assertEquals('', $record->responseid);
864
        $this->assertEquals('', $record->fingerprint);
865
        $this->assertEquals('', $record->generatedcontent);
866
 
867
        $record = $DB->get_record('ai_action_generate_text', ['id' => $actionid2]);
868
        $this->assertNotEquals('', $record->prompt);
869
        $this->assertNotEquals('', $record->responseid);
870
        $this->assertNotEquals('', $record->fingerprint);
871
        $this->assertNotEquals('', $record->generatedcontent);
872
    }
873
 
874
    /**
875
     * Test delete_data_for_all_users_in_context() for generate image.
876
     */
877
    public function test_delete_data_for_user_for_generate_image(): void {
878
        global $DB;
879
        $user1 = $this->getDataGenerator()->create_user();
880
        $user2 = $this->getDataGenerator()->create_user();
881
        $course = $this->getDataGenerator()->create_course();
882
        $coursecontext = \context_course::instance($course->id);
883
        $clock = $this->mock_clock_with_frozen();
884
 
885
        $action = new generate_image(
886
            contextid: $coursecontext->id,
887
            userid: $user1->id,
888
            prompttext: 'This is a test prompt 1',
889
            quality: 'hd',
890
            aspectratio: 'square',
891
            numimages: 1,
892
            style: 'vivid',
893
        );
894
 
895
        $body = [
896
            'revisedprompt' => 'This is a revised prompt',
897
            'sourceurl' => 'https://example.com/image1.png',
898
            'model' => 'dall-e-3',
899
        ];
900
        $actionresponse = new response_generate_image(
901
            success: true,
902
        );
903
        $actionresponse->set_response_data($body);
904
 
905
        $method = new \ReflectionMethod($this->manager, 'store_action_result');
906
        $storeresult1 = $method->invoke($this->manager, $this->provider, $action, $actionresponse);
907
 
908
        $clock->bump(2);
909
        $action = new generate_image(
910
            contextid: $coursecontext->id,
911
            userid: $user2->id,
912
            prompttext: 'This is a test prompt 2',
913
            quality: 'hd',
914
            aspectratio: 'square',
915
            numimages: 1,
916
            style: 'vivid',
917
        );
918
 
919
        $body = [
920
            'revisedprompt' => 'This is a revised prompt',
921
            'sourceurl' => 'https://example.com/image2.png',
922
            'model' => 'dall-e-3',
923
        ];
924
        $actionresponse = new response_generate_image(
925
            success: true,
926
        );
927
        $actionresponse->set_response_data($body);
928
        $method = new \ReflectionMethod($this->manager, 'store_action_result');
929
        $storeresult2 = $method->invoke($this->manager, $this->provider, $action, $actionresponse);
930
 
931
        $contextlist = provider::get_contexts_for_userid($user1->id);
932
        $approvedcontextlist = new approved_contextlist($user1, 'core_ai', $contextlist->get_contextids());
933
        provider::delete_data_for_user($approvedcontextlist);
934
 
935
        $actionid1 = $DB->get_field('ai_action_register', 'actionid', ['id' => $storeresult1]);
936
        $actionid2 = $DB->get_field('ai_action_register', 'actionid', ['id' => $storeresult2]);
937
 
938
        $record = $DB->get_record('ai_action_generate_image', ['id' => $actionid1]);
939
        $this->assertEquals('', $record->prompt);
940
        $this->assertEquals('', $record->sourceurl);
941
        $this->assertEquals('', $record->revisedprompt);
942
 
943
        $record = $DB->get_record('ai_action_generate_image', ['id' => $actionid2]);
944
        $this->assertNotEquals('', $record->prompt);
945
        $this->assertNotEquals('', $record->sourceurl);
946
        $this->assertNotEquals('', $record->revisedprompt);
947
    }
948
 
949
    /**
950
     * Test delete_data_for_all_users_in_context() for summarise text.
951
     */
952
    public function test_delete_data_for_user_for_summarise_text(): void {
953
        global $DB;
954
        $user1 = $this->getDataGenerator()->create_user();
955
        $user2 = $this->getDataGenerator()->create_user();
956
        $course = $this->getDataGenerator()->create_course();
957
        $coursecontext = \context_course::instance($course->id);
958
        $clock = $this->mock_clock_with_frozen();
959
 
960
        $action = new summarise_text(
961
            contextid: $coursecontext->id,
962
            userid: $user1->id,
963
            prompttext: 'This is a test prompt 1',
964
        );
965
 
966
        $body = [
967
            'id' => 'chatcmpl-123',
968
            'fingerprint' => 'fp_44709d6fcb',
969
            'generatedcontent' => 'This is the generated content 1',
970
            'finishreason' => 'stop',
971
            'prompttokens' => 9,
972
            'completiontokens' => 12,
973
            'model' => 'gpt-4o',
974
        ];
975
        $actionresponse = new response_summarise_text(
976
            success: true,
977
        );
978
        $actionresponse->set_response_data($body);
979
 
980
        $method = new \ReflectionMethod($this->manager, 'store_action_result');
981
        $storeresult1 = $method->invoke($this->manager, $this->provider, $action, $actionresponse);
982
 
983
        $clock->bump(2);
984
        $action = new summarise_text(
985
            contextid: $coursecontext->id,
986
            userid: $user2->id,
987
            prompttext: 'This is a test prompt 2',
988
        );
989
 
990
        $body = [
991
            'id' => 'chatcmpl-123',
992
            'fingerprint' => 'fp_44709d6fcb',
993
            'generatedcontent' => 'This is the generated content 2',
994
            'finishreason' => 'stop',
995
            'prompttokens' => 10,
996
            'completiontokens' => 15,
997
            'model' => 'gpt-4o',
998
        ];
999
        $actionresponse = new response_summarise_text(
1000
            success: true,
1001
        );
1002
        $actionresponse->set_response_data($body);
1003
        $method = new \ReflectionMethod($this->manager, 'store_action_result');
1004
        $storeresult2 = $method->invoke($this->manager, $this->provider, $action, $actionresponse);
1005
 
1006
        $contextlist = provider::get_contexts_for_userid($user1->id);
1007
        $approvedcontextlist = new approved_contextlist($user1, 'core_ai', $contextlist->get_contextids());
1008
        provider::delete_data_for_user($approvedcontextlist);
1009
 
1010
        $actionid1 = $DB->get_field('ai_action_register', 'actionid', ['id' => $storeresult1]);
1011
        $actionid2 = $DB->get_field('ai_action_register', 'actionid', ['id' => $storeresult2]);
1012
 
1013
        $record = $DB->get_record('ai_action_summarise_text', ['id' => $actionid1]);
1014
        $this->assertEquals('', $record->prompt);
1015
        $this->assertEquals('', $record->responseid);
1016
        $this->assertEquals('', $record->fingerprint);
1017
        $this->assertEquals('', $record->generatedcontent);
1018
 
1019
        $record = $DB->get_record('ai_action_summarise_text', ['id' => $actionid2]);
1020
        $this->assertNotEquals('', $record->prompt);
1021
        $this->assertNotEquals('', $record->responseid);
1022
        $this->assertNotEquals('', $record->fingerprint);
1023
        $this->assertNotEquals('', $record->generatedcontent);
1024
    }
1025
 
1026
    /**
1027
     * Test get_users_in_context() for policy.
1028
     */
1029
    public function test_get_users_in_context_for_policy(): void {
1030
        $user1 = $this->getDataGenerator()->create_user();
1031
        $user2 = $this->getDataGenerator()->create_user();
1032
        $course1 = $this->getDataGenerator()->create_course();
1033
        $course2 = $this->getDataGenerator()->create_course();
1034
        $course1context = \context_course::instance($course1->id);
1035
        $course2context = \context_course::instance($course2->id);
1036
 
1037
        manager::user_policy_accepted($user1->id, $course1context->id);
1038
        manager::user_policy_accepted($user2->id, $course2context->id);
1039
 
1040
        // The user list for course1context should return user1.
1041
        $userlist = new \core_privacy\local\request\userlist($course1context, 'core_ai');
1042
        provider::get_users_in_context($userlist);
1043
        $this->assertCount(1, $userlist);
1044
        $this->assertTrue(in_array($user1->id, $userlist->get_userids()));
1045
        $this->assertFalse(in_array($user2->id, $userlist->get_userids()));
1046
 
1047
        // The user list for course2context should return user2.
1048
        $userlist = new \core_privacy\local\request\userlist($course2context, 'core_ai');
1049
        provider::get_users_in_context($userlist);
1050
        $this->assertCount(1, $userlist);
1051
        $this->assertTrue(in_array($user2->id, $userlist->get_userids()));
1052
        $this->assertFalse(in_array($user1->id, $userlist->get_userids()));
1053
    }
1054
 
1055
    /**
1056
     * Test get_users_in_context() for generate text.
1057
     */
1058
    public function test_get_users_in_context_for_generate_text(): void {
1059
        global $DB;
1060
        $user1 = $this->getDataGenerator()->create_user();
1061
        $user2 = $this->getDataGenerator()->create_user();
1062
        $course1 = $this->getDataGenerator()->create_course();
1063
        $course2 = $this->getDataGenerator()->create_course();
1064
        $course1context = \context_course::instance($course1->id);
1065
        $course2context = \context_course::instance($course2->id);
1066
        $clock = $this->mock_clock_with_frozen();
1067
 
1068
        $action = new generate_text(
1069
            contextid: $course1context->id,
1070
            userid: $user1->id,
1071
            prompttext: 'This is a test prompt 1',
1072
        );
1073
 
1074
        $body = [
1075
            'id' => 'chatcmpl-123',
1076
            'fingerprint' => 'fp_44709d6fcb',
1077
            'generatedcontent' => 'This is the generated content 1',
1078
            'finishreason' => 'stop',
1079
            'prompttokens' => 9,
1080
            'completiontokens' => 12,
1081
            'model' => 'gpt-4o',
1082
        ];
1083
        $actionresponse = new response_generate_text(
1084
            success: true,
1085
        );
1086
        $actionresponse->set_response_data($body);
1087
 
1088
        $method = new \ReflectionMethod($this->manager, 'store_action_result');
1089
        $method->invoke($this->manager, $this->provider, $action, $actionresponse);
1090
 
1091
        $clock->bump(2);
1092
        $action = new generate_text(
1093
            contextid: $course2context->id,
1094
            userid: $user2->id,
1095
            prompttext: 'This is a test prompt 2',
1096
        );
1097
 
1098
        $body = [
1099
            'id' => 'chatcmpl-123',
1100
            'fingerprint' => 'fp_44709d6fcb',
1101
            'generatedcontent' => 'This is the generated content 2',
1102
            'finishreason' => 'stop',
1103
            'prompttokens' => 10,
1104
            'completiontokens' => 15,
1105
            'model' => 'gpt-4o',
1106
        ];
1107
        $actionresponse = new response_generate_text(
1108
            success: true,
1109
        );
1110
        $actionresponse->set_response_data($body);
1111
        $method = new \ReflectionMethod($this->manager, 'store_action_result');
1112
        $method->invoke($this->manager, $this->provider, $action, $actionresponse);
1113
 
1114
        // The user list for course1context should return user1.
1115
        $userlist = new \core_privacy\local\request\userlist($course1context, 'core_ai');
1116
        provider::get_users_in_context($userlist);
1117
        $this->assertCount(1, $userlist);
1118
        $this->assertTrue(in_array($user1->id, $userlist->get_userids()));
1119
        $this->assertFalse(in_array($user2->id, $userlist->get_userids()));
1120
 
1121
        // The user list for course2context should return user2.
1122
        $userlist = new \core_privacy\local\request\userlist($course2context, 'core_ai');
1123
        provider::get_users_in_context($userlist);
1124
        $this->assertCount(1, $userlist);
1125
        $this->assertTrue(in_array($user2->id, $userlist->get_userids()));
1126
        $this->assertFalse(in_array($user1->id, $userlist->get_userids()));
1127
    }
1128
 
1129
    /**
1130
     * Test get_users_in_context() for generate image.
1131
     */
1132
    public function test_get_users_in_context_for_generate_image(): void {
1133
        $user1 = $this->getDataGenerator()->create_user();
1134
        $user2 = $this->getDataGenerator()->create_user();
1135
        $course1 = $this->getDataGenerator()->create_course();
1136
        $course2 = $this->getDataGenerator()->create_course();
1137
        $course1context = \context_course::instance($course1->id);
1138
        $course2context = \context_course::instance($course2->id);
1139
        $clock = $this->mock_clock_with_frozen();
1140
 
1141
        $action = new generate_image(
1142
            contextid: $course1context->id,
1143
            userid: $user1->id,
1144
            prompttext: 'This is a test prompt 1',
1145
            quality: 'hd',
1146
            aspectratio: 'square',
1147
            numimages: 1,
1148
            style: 'vivid',
1149
        );
1150
 
1151
        $body = [
1152
            'revisedprompt' => 'This is a revised prompt',
1153
            'sourceurl' => 'https://example.com/image1.png',
1154
            'model' => 'dall-e-3',
1155
        ];
1156
        $actionresponse = new response_generate_image(
1157
            success: true,
1158
        );
1159
        $actionresponse->set_response_data($body);
1160
 
1161
        $method = new \ReflectionMethod($this->manager, 'store_action_result');
1162
        $method->invoke($this->manager, $this->provider, $action, $actionresponse);
1163
 
1164
        $clock->bump(2);
1165
        $action = new generate_image(
1166
            contextid: $course2context->id,
1167
            userid: $user2->id,
1168
            prompttext: 'This is a test prompt 2',
1169
            quality: 'hd',
1170
            aspectratio: 'square',
1171
            numimages: 1,
1172
            style: 'vivid',
1173
        );
1174
 
1175
        $body = [
1176
            'revisedprompt' => 'This is a revised prompt',
1177
            'sourceurl' => 'https://example.com/image2.png',
1178
            'model' => 'dall-e-3',
1179
        ];
1180
        $actionresponse = new response_generate_image(
1181
            success: true,
1182
        );
1183
        $actionresponse->set_response_data($body);
1184
        $method = new \ReflectionMethod($this->manager, 'store_action_result');
1185
        $method->invoke($this->manager, $this->provider, $action, $actionresponse);
1186
 
1187
        // The user list for course1context should return user1.
1188
        $userlist = new \core_privacy\local\request\userlist($course1context, 'core_ai');
1189
        provider::get_users_in_context($userlist);
1190
        $this->assertCount(1, $userlist);
1191
        $this->assertTrue(in_array($user1->id, $userlist->get_userids()));
1192
        $this->assertFalse(in_array($user2->id, $userlist->get_userids()));
1193
 
1194
        // The user list for course2context should return user2.
1195
        $userlist = new \core_privacy\local\request\userlist($course2context, 'core_ai');
1196
        provider::get_users_in_context($userlist);
1197
        $this->assertCount(1, $userlist);
1198
        $this->assertTrue(in_array($user2->id, $userlist->get_userids()));
1199
        $this->assertFalse(in_array($user1->id, $userlist->get_userids()));
1200
    }
1201
 
1202
    /**
1203
     * Test get_users_in_context() for summarise text.
1204
     */
1205
    public function test_get_users_in_context_for_summarise_text(): void {
1206
        $user1 = $this->getDataGenerator()->create_user();
1207
        $user2 = $this->getDataGenerator()->create_user();
1208
        $course1 = $this->getDataGenerator()->create_course();
1209
        $course2 = $this->getDataGenerator()->create_course();
1210
        $course1context = \context_course::instance($course1->id);
1211
        $course2context = \context_course::instance($course2->id);
1212
        $clock = $this->mock_clock_with_frozen();
1213
 
1214
        $action = new summarise_text(
1215
            contextid: $course1context->id,
1216
            userid: $user1->id,
1217
            prompttext: 'This is a test prompt 1',
1218
        );
1219
 
1220
        $body = [
1221
            'id' => 'chatcmpl-123',
1222
            'fingerprint' => 'fp_44709d6fcb',
1223
            'generatedcontent' => 'This is the generated content 1',
1224
            'finishreason' => 'stop',
1225
            'prompttokens' => 9,
1226
            'completiontokens' => 12,
1227
            'model' => 'gpt-4o',
1228
        ];
1229
        $actionresponse = new response_summarise_text(
1230
            success: true,
1231
        );
1232
        $actionresponse->set_response_data($body);
1233
 
1234
        $method = new \ReflectionMethod($this->manager, 'store_action_result');
1235
        $method->invoke($this->manager, $this->provider, $action, $actionresponse);
1236
 
1237
        $clock->bump(2);
1238
        $action = new summarise_text(
1239
            contextid: $course2context->id,
1240
            userid: $user2->id,
1241
            prompttext: 'This is a test prompt 2',
1242
        );
1243
 
1244
        $body = [
1245
            'id' => 'chatcmpl-123',
1246
            'fingerprint' => 'fp_44709d6fcb',
1247
            'generatedcontent' => 'This is the generated content 2',
1248
            'finishreason' => 'stop',
1249
            'prompttokens' => 10,
1250
            'completiontokens' => 15,
1251
            'model' => 'gpt-4o',
1252
        ];
1253
        $actionresponse = new response_summarise_text(
1254
            success: true,
1255
        );
1256
        $actionresponse->set_response_data($body);
1257
        $method = new \ReflectionMethod($this->manager, 'store_action_result');
1258
        $method->invoke($this->manager, $this->provider, $action, $actionresponse);
1259
 
1260
        // The user list for course1context should return user1.
1261
        $userlist = new \core_privacy\local\request\userlist($course1context, 'core_ai');
1262
        provider::get_users_in_context($userlist);
1263
        $this->assertCount(1, $userlist);
1264
        $this->assertTrue(in_array($user1->id, $userlist->get_userids()));
1265
        $this->assertFalse(in_array($user2->id, $userlist->get_userids()));
1266
 
1267
        // The user list for course2context should return user2.
1268
        $userlist = new \core_privacy\local\request\userlist($course2context, 'core_ai');
1269
        provider::get_users_in_context($userlist);
1270
        $this->assertCount(1, $userlist);
1271
        $this->assertTrue(in_array($user2->id, $userlist->get_userids()));
1272
        $this->assertFalse(in_array($user1->id, $userlist->get_userids()));
1273
    }
1274
 
1275
    /**
1276
     * Test delete_data_for_users() for policy.
1277
     */
1278
    public function test_delete_data_for_users_for_policy(): void {
1279
        global $DB;
1280
        $user1 = $this->getDataGenerator()->create_user();
1281
        $user2 = $this->getDataGenerator()->create_user();
1282
        $user3 = $this->getDataGenerator()->create_user();
1283
        $course1 = $this->getDataGenerator()->create_course();
1284
        $course2 = $this->getDataGenerator()->create_course();
1285
        $course1context = \context_course::instance($course1->id);
1286
        $course2context = \context_course::instance($course2->id);
1287
 
1288
        // Set the user policy.
1289
        manager::user_policy_accepted($user1->id, $course1context->id);
1290
        manager::user_policy_accepted($user2->id, $course1context->id);
1291
        manager::user_policy_accepted($user3->id, $course2context->id);
1292
 
1293
        $userlist = new \core_privacy\local\request\userlist($course1context, 'core_ai');
1294
        provider::get_users_in_context($userlist);
1295
        $approvedlist = new approved_userlist($course1context, 'core_ai', $userlist->get_userids());
1296
        provider::delete_data_for_users($approvedlist);
1297
 
1298
        // Verify all policy data for user1 and user2 have been deleted.
1299
        $this->assertFalse($DB->record_exists('ai_policy_register', ['userid' => $user1->id]));
1300
        $this->assertFalse($DB->record_exists('ai_policy_register', ['userid' => $user2->id]));
1301
 
1302
        // Verify policy data for user3 is still present.
1303
        $this->assertTrue($DB->record_exists('ai_policy_register', ['userid' => $user3->id]));
1304
    }
1305
 
1306
    /**
1307
     * Test delete_data_for_users() for generate text.
1308
     */
1309
    public function test_delete_data_for_users_for_generate_text(): void {
1310
        global $DB;
1311
        $user1 = $this->getDataGenerator()->create_user();
1312
        $user2 = $this->getDataGenerator()->create_user();
1313
        $course1 = $this->getDataGenerator()->create_course();
1314
        $course2 = $this->getDataGenerator()->create_course();
1315
        $course1context = \context_course::instance($course1->id);
1316
        $course2context = \context_course::instance($course2->id);
1317
        $clock = $this->mock_clock_with_frozen();
1318
 
1319
        $action = new generate_text(
1320
            contextid: $course1context->id,
1321
            userid: $user1->id,
1322
            prompttext: 'This is a test prompt 1',
1323
        );
1324
 
1325
        $body = [
1326
            'id' => 'chatcmpl-123',
1327
            'fingerprint' => 'fp_44709d6fcb',
1328
            'generatedcontent' => 'This is the generated content 1',
1329
            'finishreason' => 'stop',
1330
            'prompttokens' => 9,
1331
            'completiontokens' => 12,
1332
            'model' => 'gpt-4o',
1333
        ];
1334
        $actionresponse = new response_generate_text(
1335
            success: true,
1336
        );
1337
        $actionresponse->set_response_data($body);
1338
 
1339
        $method = new \ReflectionMethod($this->manager, 'store_action_result');
1340
        $storeresult1 = $method->invoke($this->manager, $this->provider, $action, $actionresponse);
1341
 
1342
        $clock->bump(2);
1343
        $action = new generate_text(
1344
            contextid: $course2context->id,
1345
            userid: $user2->id,
1346
            prompttext: 'This is a test prompt 2',
1347
        );
1348
 
1349
        $body = [
1350
            'id' => 'chatcmpl-123',
1351
            'fingerprint' => 'fp_44709d6fcb',
1352
            'generatedcontent' => 'This is the generated content 2',
1353
            'finishreason' => 'stop',
1354
            'prompttokens' => 10,
1355
            'completiontokens' => 15,
1356
            'model' => 'gpt-4o',
1357
        ];
1358
        $actionresponse = new response_generate_text(
1359
            success: true,
1360
        );
1361
        $actionresponse->set_response_data($body);
1362
        $method = new \ReflectionMethod($this->manager, 'store_action_result');
1363
        $storeresult2 = $method->invoke($this->manager, $this->provider, $action, $actionresponse);
1364
 
1365
        $userlist = new \core_privacy\local\request\userlist($course1context, 'core_ai');
1366
        provider::get_users_in_context($userlist);
1367
        $approvedlist = new approved_userlist($course1context, 'core_ai', $userlist->get_userids());
1368
        provider::delete_data_for_users($approvedlist);
1369
 
1370
        $actionid1 = $DB->get_field('ai_action_register', 'actionid', ['id' => $storeresult1]);
1371
        $actionid2 = $DB->get_field('ai_action_register', 'actionid', ['id' => $storeresult2]);
1372
 
1373
        $record = $DB->get_record('ai_action_generate_text', ['id' => $actionid1]);
1374
        $this->assertEquals('', $record->prompt);
1375
        $this->assertEquals('', $record->responseid);
1376
        $this->assertEquals('', $record->fingerprint);
1377
        $this->assertEquals('', $record->generatedcontent);
1378
 
1379
        $record = $DB->get_record('ai_action_generate_text', ['id' => $actionid2]);
1380
        $this->assertNotEquals('', $record->prompt);
1381
        $this->assertNotEquals('', $record->responseid);
1382
        $this->assertNotEquals('', $record->fingerprint);
1383
        $this->assertNotEquals('', $record->generatedcontent);
1384
    }
1385
 
1386
    /**
1387
     * Test delete_data_for_users() for generate image.
1388
     */
1389
    public function test_delete_data_for_users_for_generate_image(): void {
1390
        global $DB;
1391
        $user1 = $this->getDataGenerator()->create_user();
1392
        $user2 = $this->getDataGenerator()->create_user();
1393
        $course1 = $this->getDataGenerator()->create_course();
1394
        $course2 = $this->getDataGenerator()->create_course();
1395
        $course1context = \context_course::instance($course1->id);
1396
        $course2context = \context_course::instance($course2->id);
1397
        $clock = $this->mock_clock_with_frozen();
1398
 
1399
        $action = new generate_image(
1400
            contextid: $course1context->id,
1401
            userid: $user1->id,
1402
            prompttext: 'This is a test prompt 1',
1403
            quality: 'hd',
1404
            aspectratio: 'square',
1405
            numimages: 1,
1406
            style: 'vivid',
1407
        );
1408
 
1409
        $body = [
1410
            'revisedprompt' => 'This is a revised prompt',
1411
            'sourceurl' => 'https://example.com/image1.png',
1412
            'model' => 'dall-e-3',
1413
        ];
1414
        $actionresponse = new response_generate_image(
1415
            success: true,
1416
        );
1417
        $actionresponse->set_response_data($body);
1418
 
1419
        $method = new \ReflectionMethod($this->manager, 'store_action_result');
1420
        $storeresult1 = $method->invoke($this->manager, $this->provider, $action, $actionresponse);
1421
 
1422
        $clock->bump(2);
1423
        $action = new generate_image(
1424
            contextid: $course2context->id,
1425
            userid: $user2->id,
1426
            prompttext: 'This is a test prompt 2',
1427
            quality: 'hd',
1428
            aspectratio: 'square',
1429
            numimages: 1,
1430
            style: 'vivid',
1431
        );
1432
 
1433
        $body = [
1434
            'revisedprompt' => 'This is a revised prompt',
1435
            'sourceurl' => 'https://example.com/image2.png',
1436
            'model' => 'dall-e-3',
1437
        ];
1438
        $actionresponse = new response_generate_image(
1439
            success: true,
1440
        );
1441
        $actionresponse->set_response_data($body);
1442
        $method = new \ReflectionMethod($this->manager, 'store_action_result');
1443
        $storeresult2 = $method->invoke($this->manager, $this->provider, $action, $actionresponse);
1444
 
1445
        $userlist = new \core_privacy\local\request\userlist($course1context, 'core_ai');
1446
        provider::get_users_in_context($userlist);
1447
        $approvedlist = new approved_userlist($course1context, 'core_ai', $userlist->get_userids());
1448
        provider::delete_data_for_users($approvedlist);
1449
 
1450
        $actionid1 = $DB->get_field('ai_action_register', 'actionid', ['id' => $storeresult1]);
1451
        $actionid2 = $DB->get_field('ai_action_register', 'actionid', ['id' => $storeresult2]);
1452
 
1453
        $record = $DB->get_record('ai_action_generate_image', ['id' => $actionid1]);
1454
        $this->assertEquals('', $record->prompt);
1455
        $this->assertEquals('', $record->sourceurl);
1456
        $this->assertEquals('', $record->revisedprompt);
1457
 
1458
        $record = $DB->get_record('ai_action_generate_image', ['id' => $actionid2]);
1459
        $this->assertNotEquals('', $record->prompt);
1460
        $this->assertNotEquals('', $record->sourceurl);
1461
        $this->assertNotEquals('', $record->revisedprompt);
1462
    }
1463
 
1464
    /**
1465
     * Test delete_data_for_users() for generate image.
1466
     */
1467
    public function test_delete_data_for_users_for_summarise_text(): void {
1468
        global $DB;
1469
        $user = $this->getDataGenerator()->create_user();
1470
        $course1 = $this->getDataGenerator()->create_course();
1471
        $course2 = $this->getDataGenerator()->create_course();
1472
        $course1context = \context_course::instance($course1->id);
1473
        $course2context = \context_course::instance($course2->id);
1474
        $clock = $this->mock_clock_with_frozen();
1475
 
1476
        $action = new summarise_text(
1477
            contextid: $course1context->id,
1478
            userid: $user->id,
1479
            prompttext: 'This is a test prompt 1',
1480
        );
1481
 
1482
        $body = [
1483
            'id' => 'chatcmpl-123',
1484
            'fingerprint' => 'fp_44709d6fcb',
1485
            'generatedcontent' => 'This is the generated content 1',
1486
            'finishreason' => 'stop',
1487
            'prompttokens' => 9,
1488
            'completiontokens' => 12,
1489
            'model' => 'gpt-4o',
1490
        ];
1491
        $actionresponse = new response_summarise_text(
1492
            success: true,
1493
        );
1494
        $actionresponse->set_response_data($body);
1495
 
1496
        $method = new \ReflectionMethod($this->manager, 'store_action_result');
1497
        $storeresult1 = $method->invoke($this->manager, $this->provider, $action, $actionresponse);
1498
 
1499
        $clock->bump(2);
1500
        $action = new summarise_text(
1501
            contextid: $course2context->id,
1502
            userid: $user->id,
1503
            prompttext: 'This is a test prompt 2',
1504
        );
1505
 
1506
        $body = [
1507
            'id' => 'chatcmpl-123',
1508
            'fingerprint' => 'fp_44709d6fcb',
1509
            'generatedcontent' => 'This is the generated content 2',
1510
            'finishreason' => 'stop',
1511
            'prompttokens' => 10,
1512
            'completiontokens' => 15,
1513
            'model' => 'gpt-4o',
1514
        ];
1515
        $actionresponse = new response_summarise_text(
1516
            success: true,
1517
        );
1518
        $actionresponse->set_response_data($body);
1519
        $method = new \ReflectionMethod($this->manager, 'store_action_result');
1520
        $storeresult2 = $method->invoke($this->manager, $this->provider, $action, $actionresponse);
1521
 
1522
        $userlist = new \core_privacy\local\request\userlist($course1context, 'core_ai');
1523
        provider::get_users_in_context($userlist);
1524
        $approvedlist = new approved_userlist($course1context, 'core_ai', $userlist->get_userids());
1525
        provider::delete_data_for_users($approvedlist);
1526
 
1527
        $actionid1 = $DB->get_field('ai_action_register', 'actionid', ['id' => $storeresult1]);
1528
        $actionid2 = $DB->get_field('ai_action_register', 'actionid', ['id' => $storeresult2]);
1529
 
1530
        $record = $DB->get_record('ai_action_summarise_text', ['id' => $actionid1]);
1531
        $this->assertEquals('', $record->prompt);
1532
        $this->assertEquals('', $record->responseid);
1533
        $this->assertEquals('', $record->fingerprint);
1534
        $this->assertEquals('', $record->generatedcontent);
1535
 
1536
        $record = $DB->get_record('ai_action_summarise_text', ['id' => $actionid2]);
1537
        $this->assertNotEquals('', $record->prompt);
1538
        $this->assertNotEquals('', $record->responseid);
1539
        $this->assertNotEquals('', $record->fingerprint);
1540
        $this->assertNotEquals('', $record->generatedcontent);
1541
    }
1542
 
1543
    /**
1544
     * Test get_name.
1545
     */
1546
    public function test_get_name(): void {
1547
        $this->assertEquals('aiprovider_openai', $this->provider->get_name());
1548
    }
1549
 
1550
    /**
1551
     * Test the is_request_allowed method of the provider abstract class.
1552
     */
1553
    public function test_is_request_allowed(): void {
1554
        // Create action.
1555
        $action1 = new summarise_text(
1556
            contextid: 1,
1557
            userid: 1,
1558
            prompttext: 'This is a test prompt 1',
1559
        );
1560
        $action2 = new summarise_text(
1561
            contextid: 1,
1562
            userid: 2,
1563
            prompttext: 'This is a test prompt 1',
1564
        );
1565
 
1566
        // Create the provider instance.
1567
        $config = [
1568
            'enableuserratelimit' => true,
1569
            'userratelimit' => 3,
1570
            'enableglobalratelimit' => true,
1571
            'globalratelimit' => 5,
1572
        ];
1573
        $provider = $this->manager->create_provider_instance(
1574
                classname: '\aiprovider_openai\provider',
1575
                name: 'dummy',
1576
                config: $config,
1577
        );
1578
        // Make 3 requests for the first user, all should be allowed.
1579
        for ($i = 0; $i < 3; $i++) {
1580
            $this->assertTrue($provider->is_request_allowed($action1));
1581
        }
1582
 
1583
        // The 4th request should be denied.
1584
        $this->assertFalse($provider->is_request_allowed($action1)['success']);
1585
 
1586
        // Make 2 requests for the second user, all should be allowed.
1587
        for ($i = 0; $i < 2; $i++) {
1588
            $this->assertTrue($provider->is_request_allowed($action2));
1589
        }
1590
 
1591
        // THe final request should be denied.
1592
        $this->assertFalse($provider->is_request_allowed($action2)['success']);
1593
    }
1594
}