1
+ """Tests for auth_init script functionality."""
2
+
3
+ import asyncio
4
+ import os
5
+ import sys
6
+ from unittest .mock import AsyncMock , Mock , patch
7
+
8
+ import pytest
9
+
10
+ # Add the scripts directory to the path so we can import the modules
11
+ sys .path .insert (0 , os .path .join (os .path .dirname (__file__ ), '..' , 'scripts' ))
12
+
13
+ from auth_init import create_or_update_application_with_secret , update_azd_env
14
+ from auth_common import get_application
15
+
16
+
17
+ class MockApplication :
18
+ """Mock Application object for testing."""
19
+ def __init__ (self , display_name = "Test App" ):
20
+ self .display_name = display_name
21
+
22
+
23
+ class MockGraphClient :
24
+ """Mock GraphServiceClient for testing."""
25
+ def __init__ (self ):
26
+ self .applications = Mock ()
27
+ self .applications_with_app_id = Mock ()
28
+
29
+
30
+ @pytest .mark .asyncio
31
+ async def test_create_or_update_application_with_secret_regenerates_when_app_recreated ():
32
+ """Test that secrets are regenerated when applications are recreated."""
33
+
34
+ # Mock environment variables - simulating the case where app was deleted but env vars remain
35
+ with patch .dict (os .environ , {
36
+ 'AZURE_SERVER_APP_ID' : 'old-app-id' ,
37
+ 'AZURE_SERVER_APP_SECRET' : 'old-secret-from-deleted-app'
38
+ }):
39
+
40
+ # Mock graph client
41
+ graph_client = MockGraphClient ()
42
+
43
+ # Mock get_application to return None (app doesn't exist anymore)
44
+ with patch ('auth_init.get_application' , return_value = None ):
45
+ # Mock create_application to return new app details
46
+ with patch ('auth_init.create_application' , return_value = ('new-object-id' , 'new-app-id' )):
47
+ # Mock add_client_secret to return new secret
48
+ with patch ('auth_init.add_client_secret' , return_value = 'new-secret' ) as mock_add_secret :
49
+ # Mock update_azd_env to track environment updates
50
+ with patch ('auth_init.update_azd_env' ) as mock_update_env :
51
+
52
+ # Call the function
53
+ object_id , app_id , created_app = await create_or_update_application_with_secret (
54
+ graph_client ,
55
+ app_id_env_var = 'AZURE_SERVER_APP_ID' ,
56
+ app_secret_env_var = 'AZURE_SERVER_APP_SECRET' ,
57
+ request_app = MockApplication ()
58
+ )
59
+
60
+ # Verify that a new application was created
61
+ assert created_app is True
62
+ assert object_id == 'new-object-id'
63
+ assert app_id == 'new-app-id'
64
+
65
+ # Verify that add_client_secret was called (secret was regenerated)
66
+ mock_add_secret .assert_called_once_with (graph_client , 'new-object-id' )
67
+
68
+ # Verify that the environment was updated with the new secret
69
+ mock_update_env .assert_any_call ('AZURE_SERVER_APP_SECRET' , 'new-secret' )
70
+
71
+
72
+ @pytest .mark .asyncio
73
+ async def test_create_or_update_application_with_secret_skips_when_app_exists_and_secret_exists ():
74
+ """Test that secrets are NOT regenerated when app exists and secret exists."""
75
+
76
+ # Mock environment variables - app and secret both exist
77
+ with patch .dict (os .environ , {
78
+ 'AZURE_SERVER_APP_ID' : 'existing-app-id' ,
79
+ 'AZURE_SERVER_APP_SECRET' : 'existing-secret'
80
+ }):
81
+
82
+ # Mock graph client
83
+ graph_client = MockGraphClient ()
84
+ graph_client .applications .by_application_id .return_value .patch = AsyncMock ()
85
+
86
+ # Mock get_application to return existing app
87
+ with patch ('auth_init.get_application' , return_value = 'existing-object-id' ):
88
+ # Mock add_client_secret (should not be called)
89
+ with patch ('auth_init.add_client_secret' ) as mock_add_secret :
90
+ # Mock update_azd_env to track environment updates
91
+ with patch ('auth_init.update_azd_env' ) as mock_update_env :
92
+
93
+ # Call the function
94
+ object_id , app_id , created_app = await create_or_update_application_with_secret (
95
+ graph_client ,
96
+ app_id_env_var = 'AZURE_SERVER_APP_ID' ,
97
+ app_secret_env_var = 'AZURE_SERVER_APP_SECRET' ,
98
+ request_app = MockApplication ()
99
+ )
100
+
101
+ # Verify that no new application was created
102
+ assert created_app is False
103
+ assert object_id == 'existing-object-id'
104
+ assert app_id == 'existing-app-id'
105
+
106
+ # Verify that add_client_secret was NOT called (secret was not regenerated)
107
+ mock_add_secret .assert_not_called ()
108
+
109
+ # Verify that the environment was NOT updated with a new secret
110
+ mock_update_env .assert_not_called ()
111
+
112
+
113
+ @pytest .mark .asyncio
114
+ async def test_create_or_update_application_with_secret_generates_when_app_exists_but_no_secret ():
115
+ """Test that secrets are generated when app exists but no secret exists."""
116
+
117
+ # Mock environment variables - app exists but no secret
118
+ with patch .dict (os .environ , {
119
+ 'AZURE_SERVER_APP_ID' : 'existing-app-id'
120
+ }, clear = False ):
121
+ # Ensure the secret env var is not set
122
+ if 'AZURE_SERVER_APP_SECRET' in os .environ :
123
+ del os .environ ['AZURE_SERVER_APP_SECRET' ]
124
+
125
+ # Mock graph client
126
+ graph_client = MockGraphClient ()
127
+ graph_client .applications .by_application_id .return_value .patch = AsyncMock ()
128
+
129
+ # Mock get_application to return existing app
130
+ with patch ('auth_init.get_application' , return_value = 'existing-object-id' ):
131
+ # Mock add_client_secret to return new secret
132
+ with patch ('auth_init.add_client_secret' , return_value = 'new-secret' ) as mock_add_secret :
133
+ # Mock update_azd_env to track environment updates
134
+ with patch ('auth_init.update_azd_env' ) as mock_update_env :
135
+
136
+ # Call the function
137
+ object_id , app_id , created_app = await create_or_update_application_with_secret (
138
+ graph_client ,
139
+ app_id_env_var = 'AZURE_SERVER_APP_ID' ,
140
+ app_secret_env_var = 'AZURE_SERVER_APP_SECRET' ,
141
+ request_app = MockApplication ()
142
+ )
143
+
144
+ # Verify that no new application was created
145
+ assert created_app is False
146
+ assert object_id == 'existing-object-id'
147
+ assert app_id == 'existing-app-id'
148
+
149
+ # Verify that add_client_secret was called (secret was generated)
150
+ mock_add_secret .assert_called_once_with (graph_client , 'existing-object-id' )
151
+
152
+ # Verify that the environment was updated with the new secret
153
+ mock_update_env .assert_called_once_with ('AZURE_SERVER_APP_SECRET' , 'new-secret' )
154
+
155
+
156
+ async def run_tests ():
157
+ """Run all tests."""
158
+ print ("Running test_create_or_update_application_with_secret_regenerates_when_app_recreated..." )
159
+ await test_create_or_update_application_with_secret_regenerates_when_app_recreated ()
160
+ print ("✅ PASSED" )
161
+
162
+ print ("Running test_create_or_update_application_with_secret_skips_when_app_exists_and_secret_exists..." )
163
+ await test_create_or_update_application_with_secret_skips_when_app_exists_and_secret_exists ()
164
+ print ("✅ PASSED" )
165
+
166
+ print ("Running test_create_or_update_application_with_secret_generates_when_app_exists_but_no_secret..." )
167
+ await test_create_or_update_application_with_secret_generates_when_app_exists_but_no_secret ()
168
+ print ("✅ PASSED" )
169
+
170
+ print ("\n 🎉 All tests passed!" )
171
+
172
+
173
+ if __name__ == "__main__" :
174
+ asyncio .run (run_tests ())
0 commit comments