Skip to content

MPT-20325: Added endpoints and e2e tests for program media#305

Merged
robcsegal merged 1 commit intomainfrom
MPT-20325-add-endpoints-and-e-2-e-tests-for-program-media
Apr 17, 2026
Merged

MPT-20325: Added endpoints and e2e tests for program media#305
robcsegal merged 1 commit intomainfrom
MPT-20325-add-endpoints-and-e-2-e-tests-for-program-media

Conversation

@robcsegal
Copy link
Copy Markdown
Contributor

@robcsegal robcsegal commented Apr 16, 2026

This pull request adds full support for program media resources to the API client, including new mixins, service classes, and comprehensive tests for both sync and async usage. It introduces the ability to manage media files (such as images and videos) associated with programs, with all CRUD, publish/unpublish, and file upload/download operations covered. The changes also include new and updated tests to ensure the correctness of the implementation.

Program Media Resource Implementation

  • Added new Media model and MediaService/AsyncMediaService classes in programs_media.py to support CRUD operations, file upload/download, and publish/unpublish for program media resources. The service includes all necessary configuration and integrates with the rest of the API client.
  • Introduced MediaMixin and AsyncMediaMixin in media_mixin.py to encapsulate media-specific logic, including file and publishable operations, and updated mixins/__init__.py to export these mixins. [1] [2]

Integration with Program Services

  • Updated ProgramService and AsyncProgramService in programs.py to provide a .media(program_id) method, returning the appropriate media service for a given program. [1] [2] [3]

Testing and Fixtures

  • Added extensive unit tests for the new media services and mixins, covering all methods and model behaviors in test_programs_media.py, test_media_mixin.py, and updated test_programs.py to check new service accessors. [1] [2] [3] [4] [5]
  • Implemented new end-to-end tests for both sync and async media operations, including creation, update, deletion, retrieval, filtering, and publish/unpublish flows. New fixtures support test data and configuration. [1] [2] [3] [4] [5]

These changes ensure that program media resources are fully supported and well-tested in the API client.

Closes MPT-20325

  • Added Media resource model with fields for name, type, description, status, filename, size, content type, display order, URL, program reference, and audit reference
  • Introduced MediaService and AsyncMediaService classes for program media CRUD operations, file upload/download, and publish/unpublish functionality
  • Created MediaMixin and AsyncMediaMixin base classes composing file creation, download, and publication capabilities
  • Extended ProgramService and AsyncProgramService with .media(program_id) accessor method to access media services for a given program
  • Added comprehensive unit tests for media services and mixins covering endpoint resolution, method availability, and model instantiation
  • Added comprehensive end-to-end tests for both sync and async media operations including creation (from file and URL), update, deletion, retrieval, filtering, and publish/unpublish workflows
  • Added supporting test fixtures and configuration entries for e2e test execution

@robcsegal robcsegal requested a review from a team as a code owner April 16, 2026 17:23
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 16, 2026

📝 Walkthrough

Walkthrough

This PR introduces program media resource management by adding a Media model and dual MediaService/AsyncMediaService classes. The services compose file creation, downloading, and publishing capabilities through new mixin abstractions. Integration into ProgramsService is achieved via a media() accessor, supported by E2E and unit tests validating CRUD operations, filtering, and state transitions.

Changes

Cohort / File(s) Summary
Configuration & Fixtures
e2e_config.test.json, tests/e2e/conftest.py, tests/e2e/program/program/media/conftest.py
Added test configuration key for media, session-scoped video URL fixture, and media-specific fixtures (invalid id, media id from config, media data factory).
Mixin Definitions
mpt_api_client/resources/program/mixins/__init__.py, mpt_api_client/resources/program/mixins/media_mixin.py
Introduced MediaMixin and AsyncMediaMixin classes composing file creation, downloading, and publishing capabilities; exported via __all__.
Service Implementation
mpt_api_client/resources/program/programs_media.py
Created Media model with optional fields (name, type, description, status, filename, size, content type, display order, url, program, audit) and dual service classes (MediaService, AsyncMediaService) configured for /public/v1/program/programs/{program_id}/media endpoint.
Programs Service Integration
mpt_api_client/resources/program/programs.py
Added media(program_id: str) accessor methods to both ProgramsService and AsyncProgramsService returning respective service instances.
E2E Tests
tests/e2e/program/program/media/test_async_media.py, tests/e2e/program/program/media/test_sync_media.py
Added parallel test suites (sync/async) validating media creation (file/URL), field mapping, updates, deletion, retrieval, filtering via RQLQuery, and publish/unpublish state transitions.
Unit Tests
tests/unit/resources/program/mixin/test_media_mixin.py, tests/unit/resources/program/test_programs.py, tests/unit/resources/program/test_programs_media.py
Added mixin API existence checks with mocked file downloads, programs service property test extension, and service endpoint/configuration assertions including Media model optional-field handling.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Jira Issue Key In Title ✅ Passed The PR title contains the Jira issue key MPT-20325 in the required format at the beginning.
Test Coverage Required ✅ Passed This PR modifies 5 code files and comprehensively includes 7 test files across unit and end-to-end test suites.
Single Commit Required ✅ Passed The PR contains exactly one commit titled 'Added endpoints and e2e tests for program media', satisfying the single commit requirement.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Comment @coderabbitai help to get the list of available commands and usage tips.

@sonarqubecloud
Copy link
Copy Markdown

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (4)
tests/e2e/program/program/media/conftest.py (1)

15-27: Optional: make media_data_factory accept arbitrary overrides.

Allowing **overrides improves test ergonomics when new media fields are added, without duplicating fixture logic.

Possible refactor
 `@pytest.fixture`
 def media_data_factory():
-    def factory(media_type: str = "Image"):
-        return {
+    def factory(media_type: str = "Image", **overrides):
+        payload = {
             "name": "E2E Created Program Media",
             "description": "E2E Created Program Media",
             "displayOrder": 1,
             "type": media_type,
             "mediatype": media_type,
             "url": "",
             "language": "en-us",
         }
+        payload.update(overrides)
+        return payload
 
     return factory
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/e2e/program/program/media/conftest.py` around lines 15 - 27, The
fixture media_data_factory should accept arbitrary overrides so tests can
customize fields without changing the factory; update the inner factory function
(factory) signature to accept **overrides and merge those overrides into the
returned dict (e.g., create the base payload as currently done and then apply
overrides to it) so callers can pass any field to override defaults while
preserving existing behavior when no overrides are provided.
tests/e2e/conftest.py (1)

100-102: Consider making video_url configurable for environment stability.

A small improvement is to allow an env override while keeping this default, so E2E environments can swap video hosts/URLs without code changes.

Proposed tweak
 `@pytest.fixture`(scope="session")
 def video_url():
-    return "https://www.youtube.com/watch?v=DUMMY1_0000"
+    return os.getenv(
+        "MPT_E2E_VIDEO_URL",
+        "https://www.youtube.com/watch?v=DUMMY1_0000",
+    )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/e2e/conftest.py` around lines 100 - 102, The video_url fixture
currently returns a hardcoded URL; make it configurable via an environment
variable by having the video_url fixture read an env var (e.g., E2E_VIDEO_URL)
with the current value "https://www.youtube.com/watch?v=DUMMY1_0000" as the
fallback default and ensure imports include os in tests/e2e/conftest.py so
CI/E2E runs can override the URL without changing code.
tests/unit/resources/program/mixin/test_media_mixin.py (1)

60-93: Consider adding coverage for accept=None download flow.

A follow-up test for the implicit content-type branch (and missing-content-type error branch) would harden behavior inherited from DownloadFileMixin/AsyncDownloadFileMixin.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/unit/resources/program/mixin/test_media_mixin.py` around lines 60 - 93,
Add tests covering the implicit accept (accept=None) branch and the
missing-content-type error branch for both sync and async flows: create new
tests (e.g., test_media_download_accept_none and
test_media_download_missing_content_type, plus async equivalents) that call
media_service.download("MED-123", accept=None) and await
async_media_service.download(..., accept=None) and assert normal behavior when
the response includes a Content-Type header, and create mocks returning
responses WITHOUT a Content-Type header to assert the
DownloadFileMixin/AsyncDownloadFileMixin raises the expected error; use the same
respx/httpx pattern and assert mock_route.call_count, request.method, and either
result.file_contents or that the appropriate exception is raised.
tests/unit/resources/program/test_programs_media.py (1)

86-97: Optional-field absence test misses three declared optional fields

Media also declares display_order, url, and program as optional. Adding them here would make the regression guard complete.

Suggested enhancement
 def test_media_optional_fields_absent():
     result = Media({"id": "PMD-001"})

     assert result.id == "PMD-001"
     assert not hasattr(result, "name")
     assert not hasattr(result, "type")
     assert not hasattr(result, "description")
     assert not hasattr(result, "status")
     assert not hasattr(result, "filename")
     assert not hasattr(result, "size")
     assert not hasattr(result, "content_type")
+    assert not hasattr(result, "display_order")
+    assert not hasattr(result, "url")
+    assert not hasattr(result, "program")
     assert not hasattr(result, "audit")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/unit/resources/program/test_programs_media.py` around lines 86 - 97,
The test_media_optional_fields_absent in
tests/unit/resources/program/test_programs_media.py is missing checks for three
optional fields declared on Media; update the test (function
test_media_optional_fields_absent) to also assert that the Media instance does
not have attributes "display_order", "url", and "program" (e.g., add not
hasattr(result, "display_order"), not hasattr(result, "url"), and not
hasattr(result, "program")) so the test covers all declared optional fields.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@tests/e2e/program/program/media/test_async_media.py`:
- Around line 26-33: The fixture created_media_from_url is supposed to test
URL-only creation but currently passes file=logo_fd to
async_vendor_media_service.create, causing it to exercise upload+URL logic;
update the fixture so created_media_from_url (and any callers) call
async_vendor_media_service.create with only the URL payload (remove the
file=logo_fd argument) or create a separate fixture name like
created_media_with_file for tests that need multipart upload, ensuring you only
pass media_data containing the "url" field in the URL-only path.

In `@tests/e2e/program/program/media/test_sync_media.py`:
- Around line 26-31: The fixture created_media_from_url is meant to test
URL-only media creation but currently passes file=logo_fd to
vendor_media_service.create which performs a multipart upload; change the call
in created_media_from_url to invoke vendor_media_service.create with only the
media_data (i.e., remove the file=logo_fd argument) so the service receives just
the URL payload and the test exercises URL-based creation; if other tests rely
on this fixture, create a separate fixture (e.g., created_media_with_file) that
supplies file=logo_fd to vendor_media_service.create instead.

---

Nitpick comments:
In `@tests/e2e/conftest.py`:
- Around line 100-102: The video_url fixture currently returns a hardcoded URL;
make it configurable via an environment variable by having the video_url fixture
read an env var (e.g., E2E_VIDEO_URL) with the current value
"https://www.youtube.com/watch?v=DUMMY1_0000" as the fallback default and ensure
imports include os in tests/e2e/conftest.py so CI/E2E runs can override the URL
without changing code.

In `@tests/e2e/program/program/media/conftest.py`:
- Around line 15-27: The fixture media_data_factory should accept arbitrary
overrides so tests can customize fields without changing the factory; update the
inner factory function (factory) signature to accept **overrides and merge those
overrides into the returned dict (e.g., create the base payload as currently
done and then apply overrides to it) so callers can pass any field to override
defaults while preserving existing behavior when no overrides are provided.

In `@tests/unit/resources/program/mixin/test_media_mixin.py`:
- Around line 60-93: Add tests covering the implicit accept (accept=None) branch
and the missing-content-type error branch for both sync and async flows: create
new tests (e.g., test_media_download_accept_none and
test_media_download_missing_content_type, plus async equivalents) that call
media_service.download("MED-123", accept=None) and await
async_media_service.download(..., accept=None) and assert normal behavior when
the response includes a Content-Type header, and create mocks returning
responses WITHOUT a Content-Type header to assert the
DownloadFileMixin/AsyncDownloadFileMixin raises the expected error; use the same
respx/httpx pattern and assert mock_route.call_count, request.method, and either
result.file_contents or that the appropriate exception is raised.

In `@tests/unit/resources/program/test_programs_media.py`:
- Around line 86-97: The test_media_optional_fields_absent in
tests/unit/resources/program/test_programs_media.py is missing checks for three
optional fields declared on Media; update the test (function
test_media_optional_fields_absent) to also assert that the Media instance does
not have attributes "display_order", "url", and "program" (e.g., add not
hasattr(result, "display_order"), not hasattr(result, "url"), and not
hasattr(result, "program")) so the test covers all declared optional fields.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Organization UI (inherited)

Review profile: CHILL

Plan: Pro

Run ID: 4c2ce7c6-d347-4b82-870f-9e196f554685

📥 Commits

Reviewing files that changed from the base of the PR and between c1d8456 and 5798712.

📒 Files selected for processing (12)
  • e2e_config.test.json
  • mpt_api_client/resources/program/mixins/__init__.py
  • mpt_api_client/resources/program/mixins/media_mixin.py
  • mpt_api_client/resources/program/programs.py
  • mpt_api_client/resources/program/programs_media.py
  • tests/e2e/conftest.py
  • tests/e2e/program/program/media/conftest.py
  • tests/e2e/program/program/media/test_async_media.py
  • tests/e2e/program/program/media/test_sync_media.py
  • tests/unit/resources/program/mixin/test_media_mixin.py
  • tests/unit/resources/program/test_programs.py
  • tests/unit/resources/program/test_programs_media.py

Comment thread tests/e2e/program/program/media/test_async_media.py
Comment thread tests/e2e/program/program/media/test_sync_media.py
Copy link
Copy Markdown
Member

@jentyk jentyk left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@robcsegal robcsegal merged commit b6e9955 into main Apr 17, 2026
4 checks passed
@robcsegal robcsegal deleted the MPT-20325-add-endpoints-and-e-2-e-tests-for-program-media branch April 17, 2026 11:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants