Skip to content

Commit be14467

Browse files
committed
Add test to simulate token and sendMail endpoints.
1 parent 88a450f commit be14467

1 file changed

Lines changed: 292 additions & 0 deletions

File tree

Lines changed: 292 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,292 @@
1+
/*
2+
* Copyright (c) 2026 LabKey Corporation
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.labkey.test.tests;
17+
18+
import jakarta.mail.Message;
19+
import jakarta.mail.MessagingException;
20+
import jakarta.mail.Session;
21+
import jakarta.mail.internet.InternetAddress;
22+
import jakarta.mail.internet.MimeMessage;
23+
import org.junit.After;
24+
import org.junit.Assert;
25+
import org.junit.Before;
26+
import org.junit.Test;
27+
import org.labkey.api.util.GraphTransportProvider;
28+
import org.mockserver.integration.ClientAndServer;
29+
30+
import java.util.Properties;
31+
32+
import static org.mockserver.model.HttpRequest.request;
33+
import static org.mockserver.model.HttpResponse.response;
34+
35+
/**
36+
* Tests for {@link GraphTransportProvider} using MockServer to simulate
37+
* Azure AD token endpoint and Microsoft Graph sendMail endpoint.
38+
*
39+
* Mocked endpoints:
40+
* POST /{tenantId}/oauth2/v2.0/token — OAuth2 client credentials token acquisition
41+
* POST /v1.0/users/{fromAddress}/sendMail — Graph API email send
42+
*/
43+
public class GraphTransportProviderTest extends Assert
44+
{
45+
private ClientAndServer mockServer;
46+
47+
// Test-generated fake values — MockServer doesn't validate these
48+
private static final String TEST_TENANT_ID = "test-tenant-id";
49+
private static final String TEST_CLIENT_ID = "test-client-id";
50+
private static final String TEST_CLIENT_SECRET = "test-client-secret";
51+
private static final String TEST_FROM_ADDRESS = "sender@example.com";
52+
private static final String TEST_TO_ADDRESS = "recipient@example.com";
53+
private static final String TEST_ACCESS_TOKEN = "mock-access-token-12345";
54+
55+
private static final String TOKEN_PATH = "/" + TEST_TENANT_ID + "/oauth2/v2.0/token";
56+
private static final String SEND_MAIL_PATH = "/v1.0/users/" + TEST_FROM_ADDRESS + "/sendMail";
57+
58+
private static final String TOKEN_RESPONSE_BODY = """
59+
{
60+
"token_type": "Bearer",
61+
"expires_in": 3600,
62+
"access_token": "%s"
63+
}
64+
""".formatted(TEST_ACCESS_TOKEN);
65+
66+
@Before
67+
public void setUp()
68+
{
69+
mockServer = ClientAndServer.startClientAndServer(0); // dynamic port
70+
}
71+
72+
@After
73+
public void tearDown()
74+
{
75+
if (mockServer != null && mockServer.isRunning())
76+
{
77+
mockServer.stop();
78+
}
79+
}
80+
81+
@Test
82+
public void testSuccessfulEmailSend() throws MessagingException
83+
{
84+
// Stub token endpoint
85+
mockServer.when(
86+
request()
87+
.withMethod("POST")
88+
.withPath(TOKEN_PATH)
89+
).respond(
90+
response()
91+
.withStatusCode(200)
92+
.withHeader("Content-Type", "application/json")
93+
.withBody(TOKEN_RESPONSE_BODY)
94+
);
95+
96+
// Stub sendMail endpoint — Graph API returns 202 Accepted
97+
mockServer.when(
98+
request()
99+
.withMethod("POST")
100+
.withPath(SEND_MAIL_PATH)
101+
.withHeader("Authorization", "Bearer " + TEST_ACCESS_TOKEN)
102+
).respond(
103+
response()
104+
.withStatusCode(202)
105+
);
106+
107+
GraphTransportProvider provider = createTestProvider();
108+
provider.send(createTestMessage());
109+
110+
// Verify both endpoints were called
111+
mockServer.verify(
112+
request()
113+
.withMethod("POST")
114+
.withPath(TOKEN_PATH)
115+
);
116+
mockServer.verify(
117+
request()
118+
.withMethod("POST")
119+
.withPath(SEND_MAIL_PATH)
120+
.withHeader("Authorization", "Bearer " + TEST_ACCESS_TOKEN)
121+
);
122+
}
123+
124+
@Test
125+
public void testTokenAcquisitionFailure()
126+
{
127+
// Stub token endpoint returning 401
128+
mockServer.when(
129+
request()
130+
.withMethod("POST")
131+
.withPath(TOKEN_PATH)
132+
).respond(
133+
response()
134+
.withStatusCode(401)
135+
.withHeader("Content-Type", "application/json")
136+
.withBody("""
137+
{
138+
"error": "invalid_client",
139+
"error_description": "Invalid client credentials"
140+
}
141+
""")
142+
);
143+
144+
GraphTransportProvider provider = createTestProvider();
145+
146+
MessagingException e = assertThrows(MessagingException.class,
147+
() -> provider.send(createTestMessage()));
148+
assertTrue("Exception should indicate token failure",
149+
e.getMessage().contains("Failed sending mail via Microsoft Graph"));
150+
}
151+
152+
@Test
153+
public void testSendMailFailure() throws MessagingException
154+
{
155+
// Stub successful token acquisition
156+
mockServer.when(
157+
request()
158+
.withMethod("POST")
159+
.withPath(TOKEN_PATH)
160+
).respond(
161+
response()
162+
.withStatusCode(200)
163+
.withHeader("Content-Type", "application/json")
164+
.withBody(TOKEN_RESPONSE_BODY)
165+
);
166+
167+
// Stub sendMail endpoint returning 400
168+
mockServer.when(
169+
request()
170+
.withMethod("POST")
171+
.withPath(SEND_MAIL_PATH)
172+
).respond(
173+
response()
174+
.withStatusCode(400)
175+
.withHeader("Content-Type", "application/json")
176+
.withBody("""
177+
{
178+
"error": {
179+
"code": "BadRequest",
180+
"message": "Invalid recipient address"
181+
}
182+
}
183+
""")
184+
);
185+
186+
GraphTransportProvider provider = createTestProvider();
187+
188+
MessagingException e = assertThrows(MessagingException.class,
189+
() -> provider.send(createTestMessage()));
190+
assertTrue("Exception should indicate Graph sendMail failure",
191+
e.getMessage().contains("Failed sending mail via Microsoft Graph"));
192+
}
193+
194+
@Test
195+
public void testTokenCaching() throws MessagingException
196+
{
197+
// Stub token endpoint
198+
mockServer.when(
199+
request()
200+
.withMethod("POST")
201+
.withPath(TOKEN_PATH)
202+
).respond(
203+
response()
204+
.withStatusCode(200)
205+
.withHeader("Content-Type", "application/json")
206+
.withBody(TOKEN_RESPONSE_BODY)
207+
);
208+
209+
// Stub sendMail endpoint
210+
mockServer.when(
211+
request()
212+
.withMethod("POST")
213+
.withPath(SEND_MAIL_PATH)
214+
).respond(
215+
response()
216+
.withStatusCode(202)
217+
);
218+
219+
GraphTransportProvider provider = createTestProvider();
220+
221+
// Send two emails with the same provider instance
222+
provider.send(createTestMessage());
223+
provider.send(createTestMessage());
224+
225+
// Token endpoint should be called only once due to caching
226+
mockServer.verify(
227+
request()
228+
.withMethod("POST")
229+
.withPath(TOKEN_PATH),
230+
org.mockserver.verify.VerificationTimes.exactly(1)
231+
);
232+
233+
// sendMail should be called twice
234+
mockServer.verify(
235+
request()
236+
.withMethod("POST")
237+
.withPath(SEND_MAIL_PATH),
238+
org.mockserver.verify.VerificationTimes.exactly(2)
239+
);
240+
}
241+
242+
/**
243+
* Creates a GraphTransportProvider with base URLs pointing to MockServer
244+
* and test credentials pre-configured.
245+
*/
246+
private GraphTransportProvider createTestProvider()
247+
{
248+
GraphTransportProvider provider = new GraphTransportProvider()
249+
{
250+
@Override
251+
protected String getOAuth2LoginBaseUrl()
252+
{
253+
return getMockBaseUrl();
254+
}
255+
256+
@Override
257+
protected String getGraphBaseUrl()
258+
{
259+
return getMockBaseUrl();
260+
}
261+
};
262+
263+
// Configure test credentials
264+
Properties props = provider.getProperties();
265+
props.put("mail.graph.tenantId", TEST_TENANT_ID);
266+
props.put("mail.graph.clientId", TEST_CLIENT_ID);
267+
props.put("mail.graph.clientSecret", TEST_CLIENT_SECRET);
268+
props.put("mail.graph.fromAddress", TEST_FROM_ADDRESS);
269+
270+
return provider;
271+
}
272+
273+
/**
274+
* Creates a simple test MimeMessage.
275+
*/
276+
private MimeMessage createTestMessage() throws MessagingException
277+
{
278+
Properties props = new Properties();
279+
Session session = Session.getDefaultInstance(props);
280+
MimeMessage message = new MimeMessage(session);
281+
message.setFrom(new InternetAddress(TEST_FROM_ADDRESS));
282+
message.setRecipient(Message.RecipientType.TO, new InternetAddress(TEST_TO_ADDRESS));
283+
message.setSubject("Test email from GraphTransportProviderTest");
284+
message.setText("This is a test email body.");
285+
return message;
286+
}
287+
288+
private String getMockBaseUrl()
289+
{
290+
return "http://localhost:" + mockServer.getLocalPort();
291+
}
292+
}

0 commit comments

Comments
 (0)