-
Notifications
You must be signed in to change notification settings - Fork 18
feat(e2e): Add files external provider tests #217
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
matthewgrossman
merged 5 commits into
main
from
mgrossman/aircore-747-e2e-tests-ngc-backed-fileset-with-ci-token
Jun 9, 2026
Merged
Changes from 1 commit
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
File renamed without changes.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,225 @@ | ||
| """E2E tests for external storage backends (NGC, Hugging Face). | ||
|
|
||
| These tests verify that the files service can create filesets backed by | ||
| external storage providers and read files from them via the SDK. | ||
|
|
||
| NGC tests require ``NGC_API_KEY`` in the environment and are skipped | ||
| otherwise. Hugging Face tests use a small public repo; when ``HF_TOKEN`` | ||
| is set the request is authenticated (avoids rate-limits in CI). | ||
| """ | ||
|
|
||
| import os | ||
| import tempfile | ||
| import uuid | ||
| from collections.abc import Iterator | ||
| from pathlib import Path | ||
|
|
||
| import pytest | ||
| from nemo_platform import NeMoPlatform | ||
| from nemo_platform.types.files import HuggingfaceStorageConfigParam, NGCStorageConfigParam | ||
|
|
||
| # --------------------------------------------------------------------------- | ||
| # NGC configuration | ||
| # --------------------------------------------------------------------------- | ||
| NGC_API_KEY_ENV = "NGC_API_KEY" | ||
|
|
||
| NGC_ORG = "nvidian" | ||
| NGC_TEAM = "nemo-llm" | ||
| NGC_TARGET = "nemo-platform-quickstart" | ||
| NGC_TARGET_TYPE = "resource" | ||
|
|
||
| # --------------------------------------------------------------------------- | ||
| # Hugging Face configuration — small public model | ||
| # --------------------------------------------------------------------------- | ||
| HF_TOKEN_ENV = "HF_TOKEN" | ||
|
|
||
| HF_REPO_ID = "hf-internal-testing/tiny-random-bert" | ||
| HF_REPO_TYPE = "model" | ||
|
|
||
| # --------------------------------------------------------------------------- | ||
| # Fixtures | ||
| # --------------------------------------------------------------------------- | ||
|
|
||
|
|
||
| @pytest.fixture | ||
| def ngc_api_key() -> str: | ||
| """Return the NGC API key from the environment.""" | ||
| key = os.environ.get(NGC_API_KEY_ENV) | ||
| assert key, f"{NGC_API_KEY_ENV} must be set" | ||
| return key | ||
|
|
||
|
|
||
| @pytest.fixture | ||
| def ngc_secret(sdk: NeMoPlatform, workspace: str, ngc_api_key: str) -> Iterator[str]: | ||
| """Create a secret containing the NGC API key, cleaned up after test.""" | ||
| secret_name = f"e2e-ngc-key-{uuid.uuid4().hex[:8]}" | ||
| sdk.secrets.create(workspace=workspace, name=secret_name, value=ngc_api_key) | ||
| yield secret_name | ||
| try: | ||
| sdk.secrets.delete(workspace=workspace, name=secret_name) | ||
| except Exception: | ||
| pass | ||
|
|
||
|
|
||
| @pytest.fixture | ||
| def ngc_fileset(sdk: NeMoPlatform, workspace: str, ngc_secret: str) -> Iterator[str]: | ||
| """Create an NGC-backed fileset, cleaned up after test.""" | ||
| fileset_name = f"e2e-ngc-fs-{uuid.uuid4().hex[:8]}" | ||
| sdk.files.filesets.create( | ||
| workspace=workspace, | ||
| name=fileset_name, | ||
| description="E2E test NGC-backed fileset", | ||
| storage=NGCStorageConfigParam( | ||
| api_key_secret=ngc_secret, | ||
| org=NGC_ORG, | ||
| team=NGC_TEAM, | ||
| target=NGC_TARGET, | ||
| target_type=NGC_TARGET_TYPE, | ||
| ), | ||
| ) | ||
| yield fileset_name | ||
| try: | ||
| sdk.files.filesets.delete(fileset_name, workspace=workspace) | ||
| except Exception: | ||
|
github-advanced-security[bot] marked this conversation as resolved.
Fixed
|
||
| pass | ||
|
|
||
|
|
||
| @pytest.fixture | ||
| def hf_secret(sdk: NeMoPlatform, workspace: str) -> Iterator[str | None]: | ||
|
matthewgrossman marked this conversation as resolved.
Outdated
|
||
| """Create a secret for the HF token if available, otherwise yield None.""" | ||
| token = os.environ.get(HF_TOKEN_ENV) | ||
| if not token: | ||
| yield None | ||
| return | ||
|
|
||
| secret_name = f"e2e-hf-tok-{uuid.uuid4().hex[:8]}" | ||
| sdk.secrets.create(workspace=workspace, name=secret_name, value=token) | ||
| yield secret_name | ||
| try: | ||
| sdk.secrets.delete(workspace=workspace, name=secret_name) | ||
| except Exception: | ||
|
github-advanced-security[bot] marked this conversation as resolved.
Fixed
|
||
| pass | ||
|
|
||
|
|
||
| @pytest.fixture | ||
| def hf_fileset(sdk: NeMoPlatform, workspace: str, hf_secret: str | None) -> Iterator[str]: | ||
| """Create a Hugging Face-backed fileset, cleaned up after test.""" | ||
| fileset_name = f"e2e-hf-fs-{uuid.uuid4().hex[:8]}" | ||
|
|
||
| storage = HuggingfaceStorageConfigParam( | ||
| repo_id=HF_REPO_ID, | ||
| repo_type=HF_REPO_TYPE, | ||
| ) | ||
| if hf_secret is not None: | ||
| storage["token_secret"] = hf_secret | ||
|
|
||
| sdk.files.filesets.create( | ||
| workspace=workspace, | ||
| name=fileset_name, | ||
| description="E2E test HF-backed fileset", | ||
| storage=storage, | ||
| ) | ||
| yield fileset_name | ||
| try: | ||
| sdk.files.filesets.delete(fileset_name, workspace=workspace) | ||
| except Exception: | ||
|
github-advanced-security[bot] marked this conversation as resolved.
Fixed
|
||
| pass | ||
|
|
||
|
|
||
| # =================================================================== | ||
| # NGC tests | ||
| # =================================================================== | ||
|
|
||
|
|
||
| @pytest.mark.skipif(not os.environ.get(NGC_API_KEY_ENV), reason=f"{NGC_API_KEY_ENV} not set") | ||
| class TestNGCFileset: | ||
| """Tests for NGC-backed filesets.""" | ||
|
|
||
| def test_list_files(self, sdk: NeMoPlatform, workspace: str, ngc_fileset: str): | ||
| """Listing an NGC-backed fileset returns files with paths and sizes.""" | ||
| files = sdk.files.list(fileset=ngc_fileset, workspace=workspace) | ||
| assert len(files.data) > 0, "NGC fileset should contain at least one file" | ||
|
|
||
| for f in files.data: | ||
| assert f.path, "Each file should have a path" | ||
| assert f.size > 0, "Each file should have a non-zero size" | ||
|
|
||
| def test_download_file(self, sdk: NeMoPlatform, workspace: str, ngc_fileset: str): | ||
| """Downloading the smallest file from an NGC fileset succeeds and size matches.""" | ||
| files = sdk.files.list(fileset=ngc_fileset, workspace=workspace) | ||
| assert len(files.data) > 0 | ||
|
|
||
| target = min(files.data, key=lambda f: f.size) | ||
|
|
||
| with tempfile.TemporaryDirectory() as tmpdir: | ||
| local_path = Path(tmpdir) / target.path.replace("/", "_") | ||
| sdk.files.download( | ||
| fileset=ngc_fileset, | ||
| workspace=workspace, | ||
| remote_path=target.path, | ||
| local_path=str(local_path), | ||
| ) | ||
| assert local_path.exists() | ||
| assert local_path.stat().st_size == target.size | ||
|
|
||
| def test_cache_status(self, sdk: NeMoPlatform, workspace: str, ngc_fileset: str): | ||
| """NGC-backed files report a cacheable status.""" | ||
| files = sdk.files.list( | ||
| fileset=ngc_fileset, | ||
| workspace=workspace, | ||
| include_cache_status=True, | ||
| ) | ||
| assert len(files.data) > 0 | ||
|
|
||
| for f in files.data: | ||
| assert f.cache_status is not None | ||
| assert f.cache_status != "not_cacheable" | ||
|
|
||
|
|
||
| # =================================================================== | ||
| # Hugging Face tests | ||
| # =================================================================== | ||
|
|
||
|
|
||
| class TestHuggingFaceFileset: | ||
| """Tests for Hugging Face-backed filesets.""" | ||
|
|
||
| def test_list_files(self, sdk: NeMoPlatform, workspace: str, hf_fileset: str): | ||
| """Listing an HF-backed fileset returns files with paths and sizes.""" | ||
| files = sdk.files.list(fileset=hf_fileset, workspace=workspace) | ||
| assert len(files.data) > 0, "HF fileset should contain at least one file" | ||
|
|
||
| for f in files.data: | ||
| assert f.path, "Each file should have a path" | ||
| assert f.size > 0, "Each file should have a non-zero size" | ||
|
|
||
| def test_download_file(self, sdk: NeMoPlatform, workspace: str, hf_fileset: str): | ||
| """Downloading the smallest file from an HF fileset succeeds and size matches.""" | ||
| files = sdk.files.list(fileset=hf_fileset, workspace=workspace) | ||
| assert len(files.data) > 0 | ||
|
|
||
| target = min(files.data, key=lambda f: f.size) | ||
|
|
||
| with tempfile.TemporaryDirectory() as tmpdir: | ||
| local_path = Path(tmpdir) / target.path.replace("/", "_") | ||
| sdk.files.download( | ||
| fileset=hf_fileset, | ||
| workspace=workspace, | ||
| remote_path=target.path, | ||
| local_path=str(local_path), | ||
| ) | ||
| assert local_path.exists() | ||
| assert local_path.stat().st_size == target.size | ||
|
|
||
| def test_cache_status(self, sdk: NeMoPlatform, workspace: str, hf_fileset: str): | ||
| """HF-backed files report a cacheable status.""" | ||
| files = sdk.files.list( | ||
| fileset=hf_fileset, | ||
| workspace=workspace, | ||
| include_cache_status=True, | ||
| ) | ||
| assert len(files.data) > 0 | ||
|
|
||
| for f in files.data: | ||
| assert f.cache_status is not None | ||
| assert f.cache_status != "not_cacheable" | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.