-
Notifications
You must be signed in to change notification settings - Fork 5k
Add tests for auth_init.py #2741
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
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,239 @@ | ||||||||||||||||||||||||||||
import os | ||||||||||||||||||||||||||||
from unittest import mock | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
import pytest | ||||||||||||||||||||||||||||
from msgraph import GraphServiceClient | ||||||||||||||||||||||||||||
from msgraph.generated.models.application import Application | ||||||||||||||||||||||||||||
from msgraph.generated.models.password_credential import PasswordCredential | ||||||||||||||||||||||||||||
from msgraph.generated.models.service_principal import ServicePrincipal | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
from .mocks import MockAzureCredential | ||||||||||||||||||||||||||||
from scripts import auth_init | ||||||||||||||||||||||||||||
from scripts.auth_init import ( | ||||||||||||||||||||||||||||
add_client_secret, | ||||||||||||||||||||||||||||
client_app, | ||||||||||||||||||||||||||||
create_application, | ||||||||||||||||||||||||||||
create_or_update_application_with_secret, | ||||||||||||||||||||||||||||
server_app_initial, | ||||||||||||||||||||||||||||
server_app_permission_setup, | ||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
@pytest.fixture | ||||||||||||||||||||||||||||
def graph_client(monkeypatch): | ||||||||||||||||||||||||||||
"""GraphServiceClient whose network layer is intercepted to avoid real HTTP calls. | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
We exercise real request builders while intercepting the adapter's send_async. | ||||||||||||||||||||||||||||
""" | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
client = GraphServiceClient(credentials=MockAzureCredential(), scopes=["https://graph.microsoft.com/.default"]) | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
calls = { | ||||||||||||||||||||||||||||
"applications.post": [], | ||||||||||||||||||||||||||||
"applications.patch": [], | ||||||||||||||||||||||||||||
"applications.add_password.post": [], | ||||||||||||||||||||||||||||
"service_principals.post": [], | ||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||
created_ids = {"object_id": "OBJ123", "client_id": "APP123"} | ||||||||||||||||||||||||||||
secret_text_value = {"value": "SECRET_VALUE"} | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
async def fake_send_async(request_info, return_type, error_mapping=None): | ||||||||||||||||||||||||||||
url = request_info.url or "" | ||||||||||||||||||||||||||||
method = ( | ||||||||||||||||||||||||||||
request_info.http_method.value | ||||||||||||||||||||||||||||
if hasattr(request_info.http_method, "value") | ||||||||||||||||||||||||||||
else str(request_info.http_method) | ||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
if method == "POST" and url.endswith("/applications"): | ||||||||||||||||||||||||||||
body = request_info.content | ||||||||||||||||||||||||||||
calls["applications.post"].append(body) | ||||||||||||||||||||||||||||
return Application( | ||||||||||||||||||||||||||||
id=created_ids["object_id"], | ||||||||||||||||||||||||||||
app_id=created_ids["client_id"], | ||||||||||||||||||||||||||||
display_name=getattr(body, "display_name", None), | ||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||
if method == "POST" and url.endswith("/servicePrincipals"): | ||||||||||||||||||||||||||||
calls["service_principals.post"].append(request_info.content) | ||||||||||||||||||||||||||||
return ServicePrincipal() | ||||||||||||||||||||||||||||
if method == "PATCH" and "/applications/" in url: | ||||||||||||||||||||||||||||
calls["applications.patch"].append(request_info.content) | ||||||||||||||||||||||||||||
return Application() | ||||||||||||||||||||||||||||
if method == "POST" and url.endswith("/addPassword"): | ||||||||||||||||||||||||||||
calls["applications.add_password.post"].append(request_info.content) | ||||||||||||||||||||||||||||
return PasswordCredential(secret_text=secret_text_value["value"]) | ||||||||||||||||||||||||||||
raise AssertionError(f"Unexpected request: {method} {url}") | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
# Patch the adapter | ||||||||||||||||||||||||||||
monkeypatch.setattr(client.request_adapter, "send_async", fake_send_async) | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
client._test_calls = calls # type: ignore[attr-defined] | ||||||||||||||||||||||||||||
client._test_secret_text_value = secret_text_value # type: ignore[attr-defined] | ||||||||||||||||||||||||||||
client._test_ids = created_ids # type: ignore[attr-defined] | ||||||||||||||||||||||||||||
return client | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
@pytest.mark.asyncio | ||||||||||||||||||||||||||||
async def test_create_application_success(graph_client): | ||||||||||||||||||||||||||||
graph = graph_client | ||||||||||||||||||||||||||||
request = server_app_initial(42) | ||||||||||||||||||||||||||||
object_id, client_id = await create_application(graph, request) | ||||||||||||||||||||||||||||
assert object_id == "OBJ123" | ||||||||||||||||||||||||||||
assert client_id == "APP123" | ||||||||||||||||||||||||||||
assert len(graph._test_calls["service_principals.post"]) == 1 | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
@pytest.mark.asyncio | ||||||||||||||||||||||||||||
async def test_create_application_missing_ids(graph_client, monkeypatch): | ||||||||||||||||||||||||||||
graph = graph_client | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
original_send_async = graph.request_adapter.send_async | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
async def bad_send_async(request_info, return_type, error_mapping=None): # type: ignore[unused-argument] | ||||||||||||||||||||||||||||
url = request_info.url or "" | ||||||||||||||||||||||||||||
method = ( | ||||||||||||||||||||||||||||
request_info.http_method.value | ||||||||||||||||||||||||||||
if hasattr(request_info.http_method, "value") | ||||||||||||||||||||||||||||
else str(request_info.http_method) | ||||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||||
if method == "POST" and url.endswith("/applications"): | ||||||||||||||||||||||||||||
return Application(id=None, app_id=None) | ||||||||||||||||||||||||||||
return await original_send_async(request_info, return_type, error_mapping) | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
monkeypatch.setattr(graph.request_adapter, "send_async", bad_send_async) | ||||||||||||||||||||||||||||
with pytest.raises(ValueError): | ||||||||||||||||||||||||||||
await create_application(graph, server_app_initial(1)) | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
@pytest.mark.asyncio | ||||||||||||||||||||||||||||
async def test_add_client_secret_success(graph_client): | ||||||||||||||||||||||||||||
graph = graph_client | ||||||||||||||||||||||||||||
secret = await add_client_secret(graph, "OBJ123") | ||||||||||||||||||||||||||||
assert secret == "SECRET_VALUE" | ||||||||||||||||||||||||||||
assert len(graph._test_calls["applications.add_password.post"]) == 1 | ||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||
@pytest.mark.asyncio | ||||||||||||||||||||||||||||
async def test_add_client_secret_missing_secret(graph_client): | ||||||||||||||||||||||||||||
graph = graph_client | ||||||||||||||||||||||||||||
graph._test_secret_text_value["value"] = None # type: ignore | ||||||||||||||||||||||||||||
|
@pytest.mark.asyncio | |
async def test_add_client_secret_missing_secret(graph_client): | |
graph = graph_client | |
graph._test_secret_text_value["value"] = None # type: ignore | |
@pytest.fixture | |
def graph_client_with_missing_secret(graph_client): | |
graph_client._test_secret_text_value["value"] = None # type: ignore | |
return graph_client | |
@pytest.mark.asyncio | |
async def test_add_client_secret_missing_secret(graph_client_with_missing_secret): | |
graph = graph_client_with_missing_secret |
Copilot uses AI. Check for mistakes.
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Setting attributes to None and suppressing type checking makes the test unclear. Consider creating a proper test fixture or using a builder pattern to create invalid server app instances.
def test_client_app_validation_errors(): | |
# Server app without api | |
server_app = server_app_initial(1) | |
server_app.api = None # type: ignore | |
def server_app_with_no_api(): | |
# Create a server app instance with api=None | |
app = server_app_initial(1) | |
app.api = None | |
return app | |
def test_client_app_validation_errors(): | |
# Server app without api | |
server_app = server_app_with_no_api() |
Copilot uses AI. Check for mistakes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Direct mutation with type ignore comments reduces test clarity. Consider creating a dedicated fixture or helper function that returns a server app with empty scopes for this test scenario.
# attach empty api | |
server_app_permission = server_app_permission_setup("server_app") | |
server_app_permission.api.oauth2_permission_scopes = [] # type: ignore | |
with pytest.raises(ValueError): | |
client_app("server_app_id", server_app_permission, 2) | |
# use a helper to create a server app with empty scopes | |
def server_app_with_empty_scopes(): | |
app = server_app_permission_setup("server_app") | |
app.api.oauth2_permission_scopes = [] | |
return app | |
with pytest.raises(ValueError): | |
client_app("server_app_id", server_app_with_empty_scopes(), 2) |
Copilot uses AI. Check for mistakes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] These test data values are magic strings that could be made more maintainable by using constants or more descriptive variable names like
MOCK_OBJECT_ID
andMOCK_CLIENT_ID
.Copilot uses AI. Check for mistakes.