Skip to content

Conversation

@kwonkwonn
Copy link
Contributor

@kwonkwonn kwonkwonn commented Dec 31, 2025

resolves #7624 (BA-3589)

Increase test coverage before migrating api/auth.py to follow pydantic model.
This Pr specifically contains 'validate_ip' and 'validate decorator' which will be used before and after migration of auth.py

Checklist: (if applicable)

  • Milestone metadata specifying the target backport version
  • [v] Mention to the original issue
  • Installer updates including:
    • Fixtures for db schema changes
    • New mandatory config options
  • Update of end-to-end CLI integration tests in ai.backend.test
  • API server-client counterparts (e.g., manager API -> client SDK)
  • [v] Test case(s) to:
    • Demonstrate the difference of before/after
    • Demonstrate the flow of abstract/conceptual models with a concrete implementation
  • Documentation
    • Contents in the docs directory
    • docstrings in public interfaces and type annotations

Copilot AI review requested due to automatic review settings December 31, 2025 02:38
@github-actions github-actions bot added the size:L 100~500 LoC label Dec 31, 2025
@cla-assistant
Copy link

cla-assistant bot commented Dec 31, 2025

CLA assistant check
All committers have signed the CLA.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR adds comprehensive unit tests for the validate_ip function and authentication decorators (auth_required, admin_required, superadmin_required) in preparation for migrating api/auth.py to follow the pydantic model. The tests establish a baseline to ensure safe refactoring with regression detection.

Key Changes

  • Added test coverage for IP validation logic (allow/deny scenarios)
  • Added test coverage for authentication decorator behavior and authorization checks
  • Added tests to verify decorator attributes are properly set

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 211 to 234
def test_validate_ip_allowed() -> None:
"""IP within CIDR range should be allowed, empty allowlist allows all."""
request = MagicMock(spec=web.Request)
request.headers = {"X-Forwarded-For": "10.0.0.50"}
request.remote = None

# Empty allowlist allows all IPs
user: dict[str, Any] = {"allowed_client_ip": None}
validate_ip(request, user)

# IP within CIDR range is allowed
user = {"allowed_client_ip": [ReadableCIDR("10.0.0.0/24", is_network=True)]}
validate_ip(request, user)


def test_validate_ip_denied() -> None:
"""IP not in allowlist should raise AuthorizationFailed."""
request = MagicMock(spec=web.Request)
request.headers = {"X-Forwarded-For": "192.168.1.100"}
request.remote = None

user: dict[str, Any] = {"allowed_client_ip": [ReadableCIDR("10.0.0.0/24", is_network=True)]}
with pytest.raises(AuthorizationFailed, match="not allowed IP address"):
validate_ip(request, user)
Copy link

Copilot AI Dec 31, 2025

Choose a reason for hiding this comment

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

The test coverage for validate_ip is incomplete. Several edge cases from the implementation are not tested:

  1. Invalid IP address format - should raise InvalidAuthParameters
  2. Missing client IP (both X-Forwarded-For and request.remote are None) - should raise AuthorizationFailed with "Not allowed IP address"
  3. Non-list allowed_client_ip value - should raise InvalidClientIPConfig
  4. Empty list allowed_client_ip behavior (currently only None is tested)

These test cases should be added to ensure comprehensive coverage before the migration mentioned in the PR description.

Copilot uses AI. Check for mistakes.
Comment on lines 22 to 25
from ai.backend.manager.errors.auth import (
AuthorizationFailed,
InvalidAuthParameters,
)
Copy link

Copilot AI Dec 31, 2025

Choose a reason for hiding this comment

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

The InvalidClientIPConfig exception is not imported but should be included to test the edge case where allowed_client_ip is not a list (line 397-398 in auth.py). Add this import to the error imports section to enable comprehensive test coverage.

Copilot uses AI. Check for mistakes.
Comment on lines 378 to 389
@pytest.mark.asyncio
async def test_superadmin_required_sets_handler_attr(self) -> None:
"""superadmin_required should set handler attributes."""

@superadmin_required
async def handler(request: web.Request) -> web.Response:
return web.Response(text="ok")

# get_handler_attr expects request.match_info.handler, so we check _backend_attrs directly
attrs = getattr(handler, "_backend_attrs", {})
assert attrs.get("auth_required", False) is True
assert attrs.get("auth_scope", None) == "superadmin"
Copy link

Copilot AI Dec 31, 2025

Choose a reason for hiding this comment

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

Missing test case for superadmin_required when the request is not authorized (is_authorized=False). Currently, only the cases where is_authorized=True with is_superadmin=True and is_superadmin=False are tested. Add a test case for when is_authorized=False to ensure complete coverage of the decorator's authorization checks, similar to test_admin_required_unauthorized.

Copilot uses AI. Check for mistakes.
Copy link
Collaborator

@HyeockJinKim HyeockJinKim left a comment

Choose a reason for hiding this comment

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

It's easier to check the implementation details while writing test code, rather than focusing on those details.
Please write test code prioritizing what behavior should be satisfied.

Additionally, please include the JIRA issue number in the PR title under "test:" sections. (e.g. test(BA-...): ...)

@HyeockJinKim
Copy link
Collaborator

And the timeline-check part in CI got stuck, please check how it applies to other PRs and changes/directories. (Refer to https://github.com/lablup/backend.ai?tab=contributing-ov-file#4-write-changelog-fragment-towncrier.)
You can see which items are available by checking the pyproject.toml file.

@kwonkwonn kwonkwonn changed the title test: add tests for validate_ip and auth decorators test(BA-3589) add tests for validate_ip and auth decorators Dec 31, 2025
@kwonkwonn kwonkwonn changed the title test(BA-3589) add tests for validate_ip and auth decorators test(BA-3589): add tests for validate_ip and auth decorators Dec 31, 2025
@HyeockJinKim HyeockJinKim force-pushed the main branch 2 times, most recently from 9552aac to 4af738e Compare December 31, 2025 15:41
@kwonkwonn kwonkwonn force-pushed the test/auth-api-coverage branch from ff73214 to 1ef5bcb Compare January 6, 2026 04:00
@github-actions github-actions bot added size:XL 500~ LoC Intern-OKR and removed size:L 100~500 LoC labels Jan 6, 2026
Copy link
Collaborator

@HyeockJinKim HyeockJinKim left a comment

Choose a reason for hiding this comment

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

I'm concerned that there are too many utility functions in this work. Ideally, we should keep test-related objects with simpler mocking objects, rather than multiple implementations. (Considering using aiohttp's Fixture as well.)

@kwonkwonn
Copy link
Contributor Author

Thanks for the review!

I thought the same thing while writing the code, but at the same time, I thought it will be easier to test other manager/api handlers which most of them follow the root_ctx: RootContext = request.app["_root.context"] pattern with this class.

If I minimize utility functions but keep APITestAppBuilder, would that be acceptable?

@kwonkwonn
Copy link
Contributor Author

Or I can fix it to look something more like tests/unit/manager/api/storage_namespace/ which will make each test file simpler.

@kwonkwonn kwonkwonn force-pushed the test/auth-api-coverage branch from 1ef5bcb to 30ccaff Compare January 6, 2026 05:31
@kwonkwonn
Copy link
Contributor Author

kwonkwonn commented Jan 6, 2026

This commit implements minimalized mocking utility for test auth.py, referenced vfolder test scenarios, i think we can move create_auth_app outside to esier referencing from other http testing.

@kwonkwonn kwonkwonn changed the title test(BA-3589): add tests for validate_ip and auth decorators test(BA-3589): add tests for manager's auth api handler Jan 6, 2026
Copy link
Collaborator

@HyeockJinKim HyeockJinKim left a comment

Choose a reason for hiding this comment

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

I think it would be better to create a fixture that registers each handler to the application and sends a request to verify it. Please take a look at https://docs.aiohttp.org/en/stable/testing.html#unittest for reference.
Could I create a sample test and share it? @kwonkwonn

Copy link
Member

@fregataa fregataa left a comment

Choose a reason for hiding this comment

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

Did we decide to implement fixture in a separate conftest file? @HyeockJinKim

@kwonkwonn
Copy link
Contributor Author

kwonkwonn commented Jan 6, 2026

Could I create a sample test and share it? @kwonkwonn

yes, If it's not bothering you so much, i will be very appreciated!
@HyeockJinKim

@kwonkwonn kwonkwonn force-pushed the test/auth-api-coverage branch from 30ccaff to 4ab96da Compare January 7, 2026 08:21
@kwonkwonn kwonkwonn marked this pull request as ready for review January 7, 2026 09:10
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +404 to +447
@pytest.mark.asyncio
async def test_returns_bad_request_on_failure(
self,
authorized_request: MagicMock,
mock_root_ctx: MagicMock,
) -> None:
"""Verify BAD_REQUEST is returned when password update fails."""
user_uuid = uuid.uuid4()
authorized_request.text = AsyncMock(
return_value=json.dumps({
"old_password": "oldpass",
"new_password": "newpass",
"new_password2": "differentpass",
})
)
authorized_request["user"] = {
"uuid": user_uuid,
"email": "[email protected]",
"domain_name": "default",
}
authorized_request["keypair"] = {"access_key": "AKTEST"}
mock_root_ctx.processors.auth.update_password.wait_for_complete = AsyncMock(
return_value=UpdatePasswordActionResult(success=False, message="mismatch")
)

response = await update_password(authorized_request)

assert response.status == HTTPStatus.BAD_REQUEST
Copy link

Copilot AI Jan 7, 2026

Choose a reason for hiding this comment

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

The test verifies that BAD_REQUEST status is returned on password update failure, but doesn't check the error message in the response body. The handler returns a specific error message 'new password mismatch' that should be verified to ensure the correct error information is provided to users.

Copilot uses AI. Check for mistakes.
Copy link
Member

@fregataa fregataa left a comment

Choose a reason for hiding this comment

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

Looks good. Please check the comment and check the hard coded values

"""Verify processor is called with correct params."""
user_uuid = uuid.uuid4()
authorized_request.text = AsyncMock(
return_value=json.dumps({"email": "[email protected]", "full_name": "New Name"})
Copy link
Member

Choose a reason for hiding this comment

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

How about using a variable for "full_name" rather than using a hard coded value? Because this should be tested by comparing

Copy link
Contributor Author

@kwonkwonn kwonkwonn Jan 7, 2026

Choose a reason for hiding this comment

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

modified "full_name" to be compared as variable.
thank you!

Copy link
Member

@fregataa fregataa Jan 7, 2026

Choose a reason for hiding this comment

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

What I mean is that we need to check all values that are compared for testing and change them to variables, not just the full_name case.

For example:

# OK
name = "hello"
result = func(name)
assert result.name == name

# NOT
result = func("hello")
assert result.name == "hello"

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oh i misunderstand that.
I'll fix it to be in variable form

Copy link
Contributor Author

Choose a reason for hiding this comment

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

For any value which is subject of comparions, I move it out from function scope and move it aside to class fields.
which can increase flexability for future test scenarios.

thank you for concise review!

@kwonkwonn kwonkwonn force-pushed the test/auth-api-coverage branch 3 times, most recently from a82a84d to 0f73da8 Compare January 7, 2026 11:21
@kwonkwonn kwonkwonn requested a review from fregataa January 7, 2026 11:28
Copy link
Member

@fregataa fregataa left a comment

Choose a reason for hiding this comment

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

LGTM

@kwonkwonn kwonkwonn force-pushed the test/auth-api-coverage branch from 0f73da8 to 5324bc3 Compare January 9, 2026 01:21
@fregataa
Copy link
Member

fregataa commented Jan 9, 2026

Please rebase to main

@kwonkwonn kwonkwonn force-pushed the test/auth-api-coverage branch from 5324bc3 to a5081cf Compare January 9, 2026 08:51
@HyeockJinKim HyeockJinKim added this pull request to the merge queue Jan 9, 2026
Merged via the queue into main with commit 9050736 Jan 9, 2026
28 checks passed
@HyeockJinKim HyeockJinKim deleted the test/auth-api-coverage branch January 9, 2026 18:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add comprehensive tests to track authorization states (pre-modification baseline)

4 participants