Skip to content

Commit 741577f

Browse files
committed
iam: add conformance tests for IAM Identity Management APIs for GCP
1 parent b5d5ab5 commit 741577f

File tree

14 files changed

+985
-3
lines changed

14 files changed

+985
-3
lines changed
Lines changed: 372 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,372 @@
1+
package com.salesforce.multicloudj.iam.client;
2+
3+
import com.salesforce.multicloudj.common.util.common.TestsUtil;
4+
import com.salesforce.multicloudj.iam.driver.AbstractIam;
5+
import com.salesforce.multicloudj.iam.model.CreateOptions;
6+
import com.salesforce.multicloudj.iam.model.TrustConfiguration;
7+
import org.junit.jupiter.api.AfterAll;
8+
import org.junit.jupiter.api.AfterEach;
9+
import org.junit.jupiter.api.Assertions;
10+
import org.junit.jupiter.api.BeforeAll;
11+
import org.junit.jupiter.api.BeforeEach;
12+
import org.junit.jupiter.api.Test;
13+
import org.junit.jupiter.api.TestInstance;
14+
15+
import java.util.List;
16+
import java.util.Optional;
17+
18+
/**
19+
* Abstract base class for IAM integration tests across different cloud providers.
20+
*
21+
* <p>This class defines the conformance test suite that all IAM provider implementations
22+
* must pass. It uses WireMock for recording and replaying HTTP interactions with cloud
23+
* provider IAM APIs.
24+
*
25+
* <p>Provider-specific test classes should extend this class and implement the
26+
* {@link #createHarness()} method to provide provider-specific configuration.
27+
*
28+
* <p>To record new test interactions, run with -Drecord system property.
29+
* Otherwise, tests will replay from recorded mappings in src/test/resources.
30+
*/
31+
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
32+
public abstract class AbstractIamIT {
33+
34+
/**
35+
* Harness interface that provider-specific test implementations must provide.
36+
* This interface encapsulates all provider-specific configuration and behavior.
37+
*/
38+
public interface Harness extends AutoCloseable {
39+
/**
40+
* Creates an IAM driver instance for testing.
41+
*
42+
* @return configured AbstractIam driver
43+
*/
44+
AbstractIam createIamDriver();
45+
46+
/**
47+
* Creates an IamClient instance for testing.
48+
*
49+
* @return configured IamClient
50+
*/
51+
IamClient createIamClient();
52+
53+
/**
54+
* Gets the test project/account/tenant ID.
55+
*
56+
* @return the tenant ID (AWS Account ID, GCP Project ID, or AliCloud Account ID)
57+
*/
58+
String getTenantId();
59+
60+
/**
61+
* Gets the test region.
62+
*
63+
* @return the region for IAM operations
64+
*/
65+
String getRegion();
66+
67+
/**
68+
* Gets a test identity name for creating service accounts/roles.
69+
*
70+
* @return the identity name
71+
*/
72+
String getTestIdentityName();
73+
74+
/**
75+
* Gets the IAM endpoint URL for the provider.
76+
*
77+
* @return the IAM endpoint URL
78+
*/
79+
String getIamEndpoint();
80+
81+
/**
82+
* Gets the provider ID.
83+
*
84+
* @return the provider ID (e.g., "aws", "gcp", "ali")
85+
*/
86+
String getProviderId();
87+
88+
/**
89+
* Gets the WireMock server port.
90+
* WireMock server needs the https port. If we make it constant at abstract class,
91+
* we won't be able to run tests in parallel. Each provider can provide a
92+
* randomly selected port number.
93+
*
94+
* @return the port number
95+
*/
96+
int getPort();
97+
98+
/**
99+
* Gets WireMock extensions if needed.
100+
* Provide the fully qualified class names here.
101+
*
102+
* @return list of WireMock extension class names
103+
*/
104+
List<String> getWiremockExtensions();
105+
106+
/**
107+
* Gets a trusted principal for trust configuration tests.
108+
*
109+
* @return a trusted principal identifier
110+
*/
111+
String getTrustedPrincipal();
112+
}
113+
114+
protected abstract Harness createHarness();
115+
116+
private Harness harness;
117+
118+
/**
119+
* Initializes the WireMock server before all tests.
120+
*/
121+
@BeforeAll
122+
public void initializeWireMockServer() {
123+
harness = createHarness();
124+
TestsUtil.startWireMockServer(
125+
"src/test/resources",
126+
harness.getPort(),
127+
harness.getWiremockExtensions().toArray(new String[0]));
128+
}
129+
130+
/**
131+
* Shuts down the WireMock server after all tests.
132+
*/
133+
@AfterAll
134+
public void shutdownWireMockServer() throws Exception {
135+
TestsUtil.stopWireMockServer();
136+
harness.close();
137+
}
138+
139+
/**
140+
* Initialize the harness and start WireMock recording.
141+
*/
142+
@BeforeEach
143+
public void setupTestEnvironment() {
144+
TestsUtil.startWireMockRecording(harness.getIamEndpoint());
145+
}
146+
147+
/**
148+
* Cleans up the test environment after each test.
149+
*/
150+
@AfterEach
151+
public void cleanupTestEnvironment() {
152+
TestsUtil.stopWireMockRecording();
153+
}
154+
155+
/**
156+
* Tests creating an identity without trust configuration.
157+
*/
158+
@Test
159+
public void testCreateIdentityWithoutTrustConfig() {
160+
IamClient iamClient = harness.createIamClient();
161+
162+
String identityId = iamClient.createIdentity(
163+
harness.getTestIdentityName(),
164+
"Test identity for MultiCloudJ integration tests",
165+
harness.getTenantId(),
166+
harness.getRegion(),
167+
Optional.empty(),
168+
Optional.empty()
169+
);
170+
171+
Assertions.assertNotNull(identityId, "Identity ID should not be null");
172+
Assertions.assertFalse(identityId.isEmpty(), "Identity ID should not be empty");
173+
}
174+
175+
/**
176+
* Tests creating an identity with trust configuration.
177+
*/
178+
@Test
179+
public void testCreateIdentityWithTrustConfig() {
180+
IamClient iamClient = harness.createIamClient();
181+
182+
TrustConfiguration trustConfig = TrustConfiguration.builder()
183+
.addTrustedPrincipal(harness.getTrustedPrincipal())
184+
.build();
185+
186+
String identityId = iamClient.createIdentity(
187+
harness.getTestIdentityName() + "-trusted",
188+
"Test identity with trust configuration",
189+
harness.getTenantId(),
190+
harness.getRegion(),
191+
Optional.of(trustConfig),
192+
Optional.empty()
193+
);
194+
195+
Assertions.assertNotNull(identityId, "Identity ID should not be null");
196+
Assertions.assertFalse(identityId.isEmpty(), "Identity ID should not be empty");
197+
}
198+
199+
/**
200+
* Tests creating an identity with CreateOptions.
201+
*/
202+
@Test
203+
public void testCreateIdentityWithOptions() {
204+
IamClient iamClient = harness.createIamClient();
205+
206+
CreateOptions options = CreateOptions.builder().build();
207+
208+
String identityId = iamClient.createIdentity(
209+
harness.getTestIdentityName() + "-options",
210+
"Test identity with options",
211+
harness.getTenantId(),
212+
harness.getRegion(),
213+
Optional.empty(),
214+
Optional.of(options)
215+
);
216+
217+
Assertions.assertNotNull(identityId, "Identity ID should not be null");
218+
Assertions.assertFalse(identityId.isEmpty(), "Identity ID should not be empty");
219+
}
220+
221+
/**
222+
* Tests creating an identity with null description.
223+
*/
224+
@Test
225+
public void testCreateIdentityWithNullDescription() {
226+
IamClient iamClient = harness.createIamClient();
227+
228+
String identityId = iamClient.createIdentity(
229+
harness.getTestIdentityName() + "-nodesc",
230+
null,
231+
harness.getTenantId(),
232+
harness.getRegion(),
233+
Optional.empty(),
234+
Optional.empty()
235+
);
236+
237+
Assertions.assertNotNull(identityId, "Identity ID should not be null");
238+
Assertions.assertFalse(identityId.isEmpty(), "Identity ID should not be empty");
239+
}
240+
241+
/**
242+
* Tests getting an identity by name.
243+
*/
244+
@Test
245+
public void testGetIdentity() {
246+
IamClient iamClient = harness.createIamClient();
247+
248+
// First create an identity
249+
String identityId = iamClient.createIdentity(
250+
harness.getTestIdentityName() + "-get",
251+
"Test identity for get operation",
252+
harness.getTenantId(),
253+
harness.getRegion(),
254+
Optional.empty(),
255+
Optional.empty()
256+
);
257+
258+
// Then retrieve it
259+
String retrievedIdentity = iamClient.getIdentity(
260+
harness.getTestIdentityName() + "-get",
261+
harness.getTenantId(),
262+
harness.getRegion()
263+
);
264+
265+
Assertions.assertNotNull(retrievedIdentity, "Retrieved identity should not be null");
266+
Assertions.assertFalse(retrievedIdentity.isEmpty(), "Retrieved identity should not be empty");
267+
}
268+
269+
/**
270+
* Tests that the provider ID is correctly set.
271+
*/
272+
@Test
273+
public void testProviderId() {
274+
AbstractIam iam = harness.createIamDriver();
275+
276+
Assertions.assertNotNull(iam.getProviderId(), "Provider ID should not be null");
277+
Assertions.assertEquals(harness.getProviderId(), iam.getProviderId(),
278+
"Provider ID should match the expected value");
279+
}
280+
281+
/**
282+
* Tests exception mapping for provider-specific exceptions.
283+
*/
284+
@Test
285+
public void testExceptionMapping() {
286+
AbstractIam iam = harness.createIamDriver();
287+
288+
// Test with a generic exception
289+
Throwable genericException = new RuntimeException("Generic error");
290+
Class<? extends com.salesforce.multicloudj.common.exceptions.SubstrateSdkException> exceptionClass =
291+
iam.getException(genericException);
292+
293+
Assertions.assertNotNull(exceptionClass, "Exception class should not be null");
294+
Assertions.assertEquals(
295+
com.salesforce.multicloudj.common.exceptions.UnknownException.class,
296+
exceptionClass,
297+
"Generic exceptions should map to UnknownException"
298+
);
299+
}
300+
301+
/**
302+
* Tests deleting an identity.
303+
*/
304+
@Test
305+
public void testDeleteIdentity() {
306+
IamClient iamClient = harness.createIamClient();
307+
308+
// First create an identity
309+
String identityId = iamClient.createIdentity(
310+
harness.getTestIdentityName() + "-delete",
311+
"Test identity for delete operation",
312+
harness.getTenantId(),
313+
harness.getRegion(),
314+
Optional.empty(),
315+
Optional.empty()
316+
);
317+
318+
Assertions.assertNotNull(identityId, "Identity ID should not be null");
319+
320+
// Then delete it - should not throw any exception
321+
Assertions.assertDoesNotThrow(() ->
322+
iamClient.deleteIdentity(
323+
harness.getTestIdentityName() + "-delete",
324+
harness.getTenantId(),
325+
harness.getRegion()
326+
)
327+
);
328+
}
329+
330+
/**
331+
* Tests the complete lifecycle: create, get, and delete an identity.
332+
*/
333+
@Test
334+
public void testIdentityLifecycle() {
335+
IamClient iamClient = harness.createIamClient();
336+
337+
String testIdentityName = harness.getTestIdentityName() + "-lifecycle";
338+
339+
// Step 1: Create an identity
340+
String identityId = iamClient.createIdentity(
341+
testIdentityName,
342+
"Test identity for lifecycle test",
343+
harness.getTenantId(),
344+
harness.getRegion(),
345+
Optional.empty(),
346+
Optional.empty()
347+
);
348+
349+
Assertions.assertNotNull(identityId, "Identity ID should not be null after creation");
350+
Assertions.assertFalse(identityId.isEmpty(), "Identity ID should not be empty after creation");
351+
352+
// Step 2: Get the identity to verify it exists
353+
String retrievedIdentity = iamClient.getIdentity(
354+
testIdentityName,
355+
harness.getTenantId(),
356+
harness.getRegion()
357+
);
358+
359+
Assertions.assertNotNull(retrievedIdentity, "Retrieved identity should not be null");
360+
Assertions.assertFalse(retrievedIdentity.isEmpty(), "Retrieved identity should not be empty");
361+
362+
// Step 3: Delete the identity
363+
Assertions.assertDoesNotThrow(() ->
364+
iamClient.deleteIdentity(
365+
testIdentityName,
366+
harness.getTenantId(),
367+
harness.getRegion()
368+
),
369+
"Deleting identity should not throw an exception"
370+
);
371+
}
372+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package com.salesforce.multicloudj.iam.client;
2+
3+
import com.salesforce.multicloudj.iam.driver.AbstractIam;
4+
5+
/**
6+
* Test helper class for creating IamClient instances in tests.
7+
* This class is in the same package as IamClient, so it can access the protected constructor.
8+
*/
9+
public class TestIamClient {
10+
11+
/**
12+
* Creates an IamClient instance for testing purposes.
13+
*
14+
* @param iam the AbstractIam driver to use
15+
* @return a new IamClient instance
16+
*/
17+
public static IamClient create(AbstractIam iam) {
18+
return new IamClient(iam);
19+
}
20+
21+
private TestIamClient() {
22+
// Utility class, prevent instantiation
23+
}
24+
}

0 commit comments

Comments
 (0)