|
1 | 1 | from datetime import timedelta |
2 | | -from unittest.mock import create_autospec, sentinel |
| 2 | +from unittest.mock import create_autospec, patch, sentinel |
3 | 3 |
|
4 | 4 | import pytest |
5 | 5 | from h_matchers import Any |
6 | 6 |
|
7 | 7 | from lms.models import Grouping, LTIParams |
8 | 8 | from lms.product.product import Routes |
9 | 9 | from lms.resources import LTILaunchResource, OAuth2RedirectResource |
10 | | -from lms.resources._js_config import JSConfig |
| 10 | +from lms.resources._js_config import JSConfig, _youtube_video_id_from_url |
11 | 11 | from lms.security import Identity, Permissions |
12 | 12 | from lms.services import HAPIError |
13 | 13 | from lms.views.api.sync import APISyncSchema |
|
22 | 22 | "h_api", |
23 | 23 | "vitalsource_service", |
24 | 24 | "jstor_service", |
| 25 | + "youtube_service", |
25 | 26 | "misc_plugin", |
26 | 27 | ) |
27 | 28 |
|
@@ -421,6 +422,94 @@ def test_jstor_sets_config(self, js_config, jstor_service, pyramid_request): |
421 | 422 | } |
422 | 423 | assert js_config.asdict()["viaUrl"] == jstor_service.via_url.return_value |
423 | 424 |
|
| 425 | + def test_youtube_assignment_sets_client_flag( |
| 426 | + self, js_config, youtube_service, course, assignment, via_url |
| 427 | + ): |
| 428 | + youtube_service.enabled = True |
| 429 | + js_config.add_document_url("https://www.youtube.com/watch?v=abc123") |
| 430 | + js_config.enable_lti_launch_mode(course, assignment) |
| 431 | + config = js_config.asdict() |
| 432 | + assert config["hypothesisClient"]["youtubeAssignment"] is True |
| 433 | + via_url.assert_called_once() |
| 434 | + |
| 435 | + @pytest.mark.parametrize( |
| 436 | + "url,sets_youtube_assignment", |
| 437 | + [ |
| 438 | + # Supported YouTube URL patterns (regex + host check) |
| 439 | + ("https://www.youtube.com/watch?v=abc123", True), |
| 440 | + ("https://youtube.com/watch?v=def456", True), |
| 441 | + ("https://youtu.be/ghi789", True), |
| 442 | + ("https://www.youtube.com/embed/jkl012", True), |
| 443 | + ("https://www.youtube.com/shorts/mno345", True), |
| 444 | + ("https://www.youtube.com/live/pqr678", True), |
| 445 | + # Negative: wrong host |
| 446 | + ("https://example.com/article", False), |
| 447 | + ("https://vimeo.com/123456", False), |
| 448 | + # Negative: wrong scheme (host would be ok but scheme fails) |
| 449 | + ("ftp://www.youtube.com/watch?v=abc", False), |
| 450 | + ], |
| 451 | + ) |
| 452 | + def test_youtube_assignment_detection_per_url_pattern( |
| 453 | + self, |
| 454 | + js_config, |
| 455 | + youtube_service, |
| 456 | + course, |
| 457 | + assignment, |
| 458 | + via_url, # noqa: ARG002 |
| 459 | + url, |
| 460 | + sets_youtube_assignment, |
| 461 | + ): |
| 462 | + """Lock down detection for each URL pattern and negative cases.""" |
| 463 | + youtube_service.enabled = True |
| 464 | + js_config.add_document_url(url) |
| 465 | + js_config.enable_lti_launch_mode(course, assignment) |
| 466 | + config = js_config.asdict() |
| 467 | + if sets_youtube_assignment: |
| 468 | + assert config["hypothesisClient"]["youtubeAssignment"] is True |
| 469 | + else: |
| 470 | + assert config["hypothesisClient"].get("youtubeAssignment") is not True |
| 471 | + |
| 472 | + def test_youtube_disabled_does_not_set_client_flag( |
| 473 | + self, |
| 474 | + js_config, |
| 475 | + youtube_service, |
| 476 | + course, |
| 477 | + assignment, |
| 478 | + via_url, # noqa: ARG002 |
| 479 | + ): |
| 480 | + youtube_service.enabled = False |
| 481 | + js_config.add_document_url("https://www.youtube.com/watch?v=abc123") |
| 482 | + js_config.enable_lti_launch_mode(course, assignment) |
| 483 | + config = js_config.asdict() |
| 484 | + assert config["hypothesisClient"].get("youtubeAssignment") is not True |
| 485 | + |
| 486 | + def test_non_youtube_url_does_not_set_client_flag( |
| 487 | + self, |
| 488 | + js_config, |
| 489 | + youtube_service, |
| 490 | + course, |
| 491 | + assignment, |
| 492 | + via_url, # noqa: ARG002 |
| 493 | + ): |
| 494 | + youtube_service.enabled = True |
| 495 | + js_config.add_document_url("https://example.com/article") |
| 496 | + js_config.enable_lti_launch_mode(course, assignment) |
| 497 | + config = js_config.asdict() |
| 498 | + assert config["hypothesisClient"].get("youtubeAssignment") is not True |
| 499 | + |
| 500 | + def test_youtube_video_id_from_url_returns_none_on_parse_error(self): |
| 501 | + """Cover the except (ValueError, AttributeError) branch.""" |
| 502 | + with patch("lms.resources._js_config.urlparse", side_effect=ValueError): |
| 503 | + assert ( |
| 504 | + _youtube_video_id_from_url("https://www.youtube.com/watch?v=abc") |
| 505 | + is None |
| 506 | + ) |
| 507 | + |
| 508 | + def test_youtube_video_id_from_url_is_case_insensitive_for_host(self): |
| 509 | + """Host is normalized so YouTube.com / YOUTUBE.COM work like the frontend.""" |
| 510 | + assert _youtube_video_id_from_url("https://YouTube.com/watch?v=xyz") == "xyz" |
| 511 | + assert _youtube_video_id_from_url("https://YOUTU.BE/xyz") == "xyz" |
| 512 | + |
424 | 513 |
|
425 | 514 | class TestAddCanvasSpeedgraderSettings: |
426 | 515 | @pytest.mark.parametrize("group_set", (sentinel.group_set, None)) |
|
0 commit comments