From f12dcbd4132be31293de125cdf5ad3e0330d2ebc Mon Sep 17 00:00:00 2001 From: Sriram Guduri Date: Wed, 12 Nov 2025 10:01:10 +0530 Subject: [PATCH] iam: add conformance tests for IAM Identity Management APIs for GCP --- .../multicloudj/iam/client/AbstractIamIT.java | 291 +++++++- iam/iam-gcp/pom.xml | 6 + .../multicloudj/iam/gcp/GcpIam.java | 6 +- .../multicloudj/iam/gcp/GcpIamIT.java | 703 +++++++++++++++--- .../src/test/resources/grpc/iam_admin.dsc | Bin 0 -> 33982 bytes .../CreateServiceAccount-testSa-0000.json | 21 + ...reateServiceAccount-testSaDelete-0000.json | 21 + .../CreateServiceAccount-testSaGet-0000.json | 21 + ...teServiceAccount-testSaLifeCycle-0000.json | 21 + ...reateServiceAccount-testSaNoDesc-0000.json | 19 + ...eateServiceAccount-testSaOptions-0000.json | 21 + ...eateServiceAccount-testSaTrusted-0000.json | 21 + .../DeleteServiceAccount-testSa-0001.json | 7 + ...eleteServiceAccount-testSaDelete-0001.json | 7 + .../DeleteServiceAccount-testSaGet-0002.json | 7 + ...teServiceAccount-testSaLifeCycle-0002.json | 7 + ...eleteServiceAccount-testSaNoDesc-0001.json | 7 + ...leteServiceAccount-testSaOptions-0001.json | 7 + ...leteServiceAccount-testSaTrusted-0003.json | 7 + .../GetIamPolicy-testSaTrusted-0001.json | 9 + .../GetServiceAccount-testSaGet-0001.json | 16 + ...etServiceAccount-testSaLifeCycle-0000.json | 16 + ...etServiceAccount-testSaLifeCycle-0001.json | 16 + .../SetIamPolicy-testSaTrusted-0002.json | 21 + .../common/util/common/TestsUtil.java | 8 +- 25 files changed, 1147 insertions(+), 139 deletions(-) create mode 100644 iam/iam-gcp/src/test/resources/grpc/iam_admin.dsc create mode 100644 iam/iam-gcp/src/test/resources/recordings/CreateServiceAccount-testSa-0000.json create mode 100644 iam/iam-gcp/src/test/resources/recordings/CreateServiceAccount-testSaDelete-0000.json create mode 100644 iam/iam-gcp/src/test/resources/recordings/CreateServiceAccount-testSaGet-0000.json create mode 100644 iam/iam-gcp/src/test/resources/recordings/CreateServiceAccount-testSaLifeCycle-0000.json create mode 100644 iam/iam-gcp/src/test/resources/recordings/CreateServiceAccount-testSaNoDesc-0000.json create mode 100644 iam/iam-gcp/src/test/resources/recordings/CreateServiceAccount-testSaOptions-0000.json create mode 100644 iam/iam-gcp/src/test/resources/recordings/CreateServiceAccount-testSaTrusted-0000.json create mode 100644 iam/iam-gcp/src/test/resources/recordings/DeleteServiceAccount-testSa-0001.json create mode 100644 iam/iam-gcp/src/test/resources/recordings/DeleteServiceAccount-testSaDelete-0001.json create mode 100644 iam/iam-gcp/src/test/resources/recordings/DeleteServiceAccount-testSaGet-0002.json create mode 100644 iam/iam-gcp/src/test/resources/recordings/DeleteServiceAccount-testSaLifeCycle-0002.json create mode 100644 iam/iam-gcp/src/test/resources/recordings/DeleteServiceAccount-testSaNoDesc-0001.json create mode 100644 iam/iam-gcp/src/test/resources/recordings/DeleteServiceAccount-testSaOptions-0001.json create mode 100644 iam/iam-gcp/src/test/resources/recordings/DeleteServiceAccount-testSaTrusted-0003.json create mode 100644 iam/iam-gcp/src/test/resources/recordings/GetIamPolicy-testSaTrusted-0001.json create mode 100644 iam/iam-gcp/src/test/resources/recordings/GetServiceAccount-testSaGet-0001.json create mode 100644 iam/iam-gcp/src/test/resources/recordings/GetServiceAccount-testSaLifeCycle-0000.json create mode 100644 iam/iam-gcp/src/test/resources/recordings/GetServiceAccount-testSaLifeCycle-0001.json create mode 100644 iam/iam-gcp/src/test/resources/recordings/SetIamPolicy-testSaTrusted-0002.json diff --git a/iam/iam-client/src/test/java/com/salesforce/multicloudj/iam/client/AbstractIamIT.java b/iam/iam-client/src/test/java/com/salesforce/multicloudj/iam/client/AbstractIamIT.java index 8e73d4bc2..f24cf8780 100644 --- a/iam/iam-client/src/test/java/com/salesforce/multicloudj/iam/client/AbstractIamIT.java +++ b/iam/iam-client/src/test/java/com/salesforce/multicloudj/iam/client/AbstractIamIT.java @@ -4,6 +4,8 @@ import com.salesforce.multicloudj.iam.driver.AbstractIam; import com.salesforce.multicloudj.iam.model.PolicyDocument; import com.salesforce.multicloudj.iam.model.Statement; +import com.salesforce.multicloudj.iam.model.CreateOptions; +import com.salesforce.multicloudj.iam.model.TrustConfiguration; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Assertions; @@ -13,6 +15,7 @@ import org.junit.jupiter.api.TestInstance; import java.util.List; +import java.util.Optional; @TestInstance(TestInstance.Lifecycle.PER_CLASS) public abstract class AbstractIamIT { @@ -33,6 +36,10 @@ public interface Harness extends AutoCloseable { String getIamEndpoint(); + String getTrustedPrincipal(); + + String getTestIdentityName(); + default String getPolicyVersion() { return ""; } @@ -41,12 +48,14 @@ default String getPolicyVersion() { List getTestPolicyActions(); - String getTestPolicyName(); - } + String getTestPolicyName(); + } protected abstract Harness createHarness(); private Harness harness; + private AbstractIam iam; + private IamClient iamClient; /** * Initializes the WireMock server before all tests. @@ -73,27 +82,29 @@ public void shutdownWireMockServer() throws Exception { @BeforeEach public void setupTestEnvironment() { TestsUtil.startWireMockRecording(harness.getIamEndpoint()); + iam = harness.createIamDriver(true); + iamClient = new IamClient(iam); } /** * Cleans up the test environment after each test. */ @AfterEach - public void cleanupTestEnvironment() { + public void cleanupTestEnvironment() throws Exception { TestsUtil.stopWireMockRecording(); + if (iamClient != null) { + iamClient.close(); // closes underlying AbstractIam + } } @Test public void testAttachInlinePolicy() { - AbstractIam iam = harness.createIamDriver(true); - IamClient iamClient = new IamClient(iam); - - Statement.StatementBuilder statementBuilder = Statement.builder() + Statement.StatementBuilder statementBuilder = Statement.builder() .effect(harness.getTestPolicyEffect()); for (String action : harness.getTestPolicyActions()) { statementBuilder.action(action); } - + PolicyDocument policyDocument = PolicyDocument.builder() .version(harness.getPolicyVersion()) .statement(statementBuilder.build()) @@ -109,10 +120,7 @@ public void testAttachInlinePolicy() { @Test public void testGetInlinePolicyDetails() { - AbstractIam iam = harness.createIamDriver(true); - IamClient iamClient = new IamClient(iam); - - PolicyDocument policyDocument = PolicyDocument.builder() + PolicyDocument policyDocument = PolicyDocument.builder() .version(harness.getPolicyVersion()) .statement(Statement.builder() .effect(harness.getTestPolicyEffect()) @@ -139,15 +147,12 @@ public void testGetInlinePolicyDetails() { @Test public void testGetAttachedPolicies() { - AbstractIam iam = harness.createIamDriver(true); - IamClient iamClient = new IamClient(iam); - - Statement.StatementBuilder statementBuilder = Statement.builder() + Statement.StatementBuilder statementBuilder = Statement.builder() .effect(harness.getTestPolicyEffect()); for (String action : harness.getTestPolicyActions()) { statementBuilder.action(action); } - + PolicyDocument policyDocument = PolicyDocument.builder() .version(harness.getPolicyVersion()) .statement(statementBuilder.build()) @@ -171,10 +176,7 @@ public void testGetAttachedPolicies() { @Test public void testRemovePolicy() { - AbstractIam iam = harness.createIamDriver(true); - IamClient iamClient = new IamClient(iam); - - PolicyDocument policyDocument = PolicyDocument.builder() + PolicyDocument policyDocument = PolicyDocument.builder() .version(harness.getPolicyVersion()) .statement(Statement.builder() .effect(harness.getTestPolicyEffect()) @@ -189,11 +191,244 @@ public void testRemovePolicy() { harness.getIdentityName() ); - iamClient.removePolicy( - harness.getIdentityName(), - harness.getTestPolicyName(), - harness.getTenantId(), - harness.getRegion() - ); - } + iamClient.removePolicy( + harness.getIdentityName(), + harness.getTestPolicyName(), + harness.getTenantId(), + harness.getRegion() + ); + } + + private void cleanUpIdentity(String identity) { + try { + iamClient.deleteIdentity( + identity, + harness.getTenantId(), + harness.getRegion() + ); + } catch (Exception e) { + // Ignore + } + } + + /** + * Tests creating an identity without trust configuration. + */ + @Test + public void testCreateIdentityWithoutTrustConfig() { + String identityName = harness.getTestIdentityName(); + String identityId = iamClient.createIdentity( + identityName, + "Test identity for MultiCloudJ integration tests", + harness.getTenantId(), + harness.getRegion(), + Optional.empty(), + Optional.empty() + ); + + Assertions.assertNotNull(identityId, "Identity ID should not be null"); + Assertions.assertFalse(identityId.isEmpty(), "Identity ID should not be empty"); + + cleanUpIdentity(identityName); + } + + /** + * Tests creating an identity with trust configuration. + */ + @Test + public void testCreateIdentityWithTrustConfig() { + String identityName = harness.getTestIdentityName() + "Trusted"; + TrustConfiguration trustConfig = TrustConfiguration.builder() + .addTrustedPrincipal(harness.getTrustedPrincipal()) + .build(); + + String identityId = iamClient.createIdentity( + identityName, + "Test identity with trust configuration", + harness.getTenantId(), + harness.getRegion(), + Optional.of(trustConfig), + Optional.empty() + ); + + Assertions.assertNotNull(identityId, "Identity ID should not be null"); + Assertions.assertFalse(identityId.isEmpty(), "Identity ID should not be empty"); + + cleanUpIdentity(identityName); + } + + /** + * Tests creating an identity with CreateOptions. + */ + @Test + public void testCreateIdentityWithOptions() { + String identityName = harness.getTestIdentityName() + "Options"; + CreateOptions options = CreateOptions.builder().build(); + + String identityId = iamClient.createIdentity( + identityName, + "Test identity with options", + harness.getTenantId(), + harness.getRegion(), + Optional.empty(), + Optional.of(options) + ); + + Assertions.assertNotNull(identityId, "Identity ID should not be null"); + Assertions.assertFalse(identityId.isEmpty(), "Identity ID should not be empty"); + + cleanUpIdentity(identityName); + } + + /** + * Tests creating an identity with null description. + */ + @Test + public void testCreateIdentityWithNullDescription() { + String identityName = harness.getTestIdentityName() + "NoDesc"; + + String identityId = iamClient.createIdentity( + identityName, + null, + harness.getTenantId(), + harness.getRegion(), + Optional.empty(), + Optional.empty() + ); + + Assertions.assertNotNull(identityId, "Identity ID should not be null"); + Assertions.assertFalse(identityId.isEmpty(), "Identity ID should not be empty"); + + cleanUpIdentity(identityName); + } + + /** + * Tests getting an identity by name. + */ + @Test + public void testGetIdentity() throws InterruptedException { + String identityName = harness.getTestIdentityName() + "Get"; + // First create an identity + String identityId = iamClient.createIdentity( + identityName, + "Test identity for get operation", + harness.getTenantId(), + harness.getRegion(), + Optional.empty(), + Optional.empty() + ); + + // sleep for 500ms + Thread.sleep(500); + + // Then retrieve it + String retrievedIdentity = iamClient.getIdentity( + harness.getTestIdentityName() + "Get", + harness.getTenantId(), + harness.getRegion() + ); + + Assertions.assertNotNull(retrievedIdentity, "Retrieved identity should not be null"); + Assertions.assertFalse(retrievedIdentity.isEmpty(), "Retrieved identity should not be empty"); + + cleanUpIdentity(identityName); + } + + /** + * Tests that the provider ID is correctly set. + */ + @Test + public void testProviderId() { + Assertions.assertNotNull(iam.getProviderId(), "Provider ID should not be null"); + Assertions.assertEquals(harness.getProviderId(), iam.getProviderId(), + "Provider ID should match the expected value"); + } + + /** + * Tests exception mapping for provider-specific exceptions. + */ + @Test + public void testExceptionMapping() { + // Test with a generic exception + Throwable genericException = new RuntimeException("Generic error"); + Class exceptionClass = + iam.getException(genericException); + + Assertions.assertNotNull(exceptionClass, "Exception class should not be null"); + Assertions.assertEquals( + com.salesforce.multicloudj.common.exceptions.UnknownException.class, + exceptionClass, + "Generic exceptions should map to UnknownException" + ); + } + + /** + * Tests deleting an identity. + */ + @Test + public void testDeleteIdentity() { + // First create an identity + String identityId = iamClient.createIdentity( + harness.getTestIdentityName() + "Delete", + "Test identity for delete operation", + harness.getTenantId(), + harness.getRegion(), + Optional.empty(), + Optional.empty() + ); + + Assertions.assertNotNull(identityId, "Identity ID should not be null"); + + // Then delete it - should not throw any exception + Assertions.assertDoesNotThrow(() -> + iamClient.deleteIdentity( + harness.getTestIdentityName() + "Delete", + harness.getTenantId(), + harness.getRegion() + ) + ); + } + + /** + * Tests the complete lifecycle: create, get, and delete an identity. + */ + @Test + public void testIdentityLifecycle() throws InterruptedException { + String testIdentityName = harness.getTestIdentityName() + "LifeCycle"; + + // Step 1: Create an identity + String identityId = iamClient.createIdentity( + testIdentityName, + "Test identity for lifecycle test", + harness.getTenantId(), + harness.getRegion(), + Optional.empty(), + Optional.empty() + ); + + Assertions.assertNotNull(identityId, "Identity ID should not be null after creation"); + Assertions.assertFalse(identityId.isEmpty(), "Identity ID should not be empty after creation"); + + // Step 2: Get the identity to verify it exists + Thread.sleep(500); + + String retrievedIdentity = iamClient.getIdentity( + testIdentityName, + harness.getTenantId(), + harness.getRegion() + ); + + Assertions.assertNotNull(retrievedIdentity, "Retrieved identity should not be null"); + Assertions.assertFalse(retrievedIdentity.isEmpty(), "Retrieved identity should not be empty"); + + // Step 3: Delete the identity + Assertions.assertDoesNotThrow(() -> + iamClient.deleteIdentity( + testIdentityName, + harness.getTenantId(), + harness.getRegion() + ), + "Deleting identity should not throw an exception" + ); + } } diff --git a/iam/iam-gcp/pom.xml b/iam/iam-gcp/pom.xml index 2ba25ea05..d74ea6c06 100644 --- a/iam/iam-gcp/pom.xml +++ b/iam/iam-gcp/pom.xml @@ -58,6 +58,12 @@ 3.12.1 test + + org.wiremock + wiremock-grpc-extension + 0.11.0 + test + com.salesforce.multicloudj iam-client diff --git a/iam/iam-gcp/src/main/java/com/salesforce/multicloudj/iam/gcp/GcpIam.java b/iam/iam-gcp/src/main/java/com/salesforce/multicloudj/iam/gcp/GcpIam.java index 4d312680a..503a48a0c 100644 --- a/iam/iam-gcp/src/main/java/com/salesforce/multicloudj/iam/gcp/GcpIam.java +++ b/iam/iam-gcp/src/main/java/com/salesforce/multicloudj/iam/gcp/GcpIam.java @@ -51,7 +51,7 @@ public GcpIam(Builder builder) { /** * Creates a new GCP service account with optional trust configuration. - * + * *

This method creates a service account in the specified GCP project. If trust configuration * is provided, it also grants the roles/iam.serviceAccountTokenCreator role to the specified * trusted principals, enabling them to impersonate this service account. @@ -469,7 +469,7 @@ private Policy removeBinding(Policy policy, String role, String member) { /** * Deletes a service account from the specified GCP project. - * + * *

This method permanently removes a service account and all its associated IAM bindings. * The operation cannot be undone. The method accepts either a service account ID or full * email address as input and constructs the appropriate resource name for the API call. @@ -499,7 +499,7 @@ protected void doDeleteIdentity(String identityName, String tenantId, String reg /** * Retrieves service account metadata from the specified GCP project. - * + * *

This method fetches details of an existing service account and returns its email address * as the unique identifier. The method accepts either a service account ID or full email address * as input and constructs the appropriate resource name for the API call. diff --git a/iam/iam-gcp/src/test/java/com/salesforce/multicloudj/iam/gcp/GcpIamIT.java b/iam/iam-gcp/src/test/java/com/salesforce/multicloudj/iam/gcp/GcpIamIT.java index 997bce3b7..f2223ad64 100644 --- a/iam/iam-gcp/src/test/java/com/salesforce/multicloudj/iam/gcp/GcpIamIT.java +++ b/iam/iam-gcp/src/test/java/com/salesforce/multicloudj/iam/gcp/GcpIamIT.java @@ -1,128 +1,619 @@ package com.salesforce.multicloudj.iam.gcp; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.github.tomakehurst.wiremock.WireMockServer; +import com.github.tomakehurst.wiremock.client.WireMock; +import com.github.tomakehurst.wiremock.core.WireMockConfiguration; import com.google.api.gax.core.FixedCredentialsProvider; +import com.google.api.gax.core.NoCredentialsProvider; +import com.google.api.gax.grpc.InstantiatingGrpcChannelProvider; import com.google.api.gax.rpc.TransportChannelProvider; import com.google.auth.oauth2.GoogleCredentials; import com.google.cloud.iam.admin.v1.IAMClient; import com.google.cloud.iam.admin.v1.IAMSettings; +import com.google.cloud.iam.admin.v1.stub.IAMStubSettings; import com.google.cloud.resourcemanager.v3.ProjectsClient; import com.google.cloud.resourcemanager.v3.ProjectsSettings; -import com.salesforce.multicloudj.common.gcp.GcpConstants; +import com.google.iam.admin.v1.ServiceAccount; +import com.google.iam.v1.Policy; +import com.google.protobuf.Descriptors; +import com.google.protobuf.DescriptorProtos; +import com.google.protobuf.Empty; +import com.google.protobuf.Message; +import com.google.protobuf.util.JsonFormat; import com.salesforce.multicloudj.common.gcp.util.MockGoogleCredentialsFactory; +import io.grpc.CallOptions; +import io.grpc.Channel; +import io.grpc.ClientCall; +import io.grpc.ClientInterceptor; +import io.grpc.ForwardingClientCall; +import io.grpc.ForwardingClientCallListener; +import io.grpc.Metadata; +import io.grpc.MethodDescriptor; +import com.salesforce.multicloudj.common.gcp.GcpConstants; import com.salesforce.multicloudj.common.gcp.util.TestsUtilGcp; import com.salesforce.multicloudj.iam.client.AbstractIamIT; import com.salesforce.multicloudj.iam.driver.AbstractIam; import org.junit.jupiter.api.Assertions; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.wiremock.grpc.GrpcExtensionFactory; +import org.wiremock.grpc.dsl.WireMockGrpcService; import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.HashSet; import java.util.List; +import java.util.Set; import java.util.concurrent.ThreadLocalRandom; +import java.util.concurrent.atomic.AtomicReference; +import java.util.stream.Stream; + +import static com.github.tomakehurst.wiremock.client.WireMock.equalToJson; +import static org.wiremock.grpc.dsl.WireMockGrpc.message; +import static org.wiremock.grpc.dsl.WireMockGrpc.method; +/** + * Integration tests for GcpIam with gRPC record/replay support. + * + *

NOTE: GCP IAM Admin API is gRPC-only. Unlike other conformance tests that use + * HTTP/HTTPS proxy-based WireMock recording, this test uses: + *

    + *
  • RECORD mode: Wraps IAMClient to intercept and save responses as JSON files
  • + *
  • REPLAY mode: Loads saved responses and registers them as WireMock gRPC stubs
  • + *
+ * + *

The test automatically generates proto descriptors and manages WireMock gRPC lifecycle. + */ public class GcpIamIT extends AbstractIamIT { - @Override - protected Harness createHarness() { - return new HarnessImpl(); - } - - public static class HarnessImpl implements AbstractIamIT.Harness { - ProjectsClient projectsClient; - IAMClient iamClient; - int port = ThreadLocalRandom.current().nextInt(1000, 10000); - - @Override - public AbstractIam createIamDriver(boolean useValidCredentials) { - boolean isRecordingEnabled = System.getProperty("record") != null; - TransportChannelProvider channelProvider = TestsUtilGcp.getTransportChannelProvider(port); - ProjectsSettings.Builder projectsSettingsBuilder = ProjectsSettings.newBuilder() - .setTransportChannelProvider(channelProvider); - try { - if (isRecordingEnabled && useValidCredentials) { - projectsClient = ProjectsClient.create(projectsSettingsBuilder.build()); - IAMSettings.Builder iamSettingsBuilder = IAMSettings.newBuilder(); - iamClient = IAMClient.create(iamSettingsBuilder.build()); - return new GcpIam.Builder() - .withProjectsClient(projectsClient) - .withIamClient(iamClient) - .build(); - } else { - GoogleCredentials mockCreds = MockGoogleCredentialsFactory.createMockCredentials(); - projectsSettingsBuilder.setCredentialsProvider(FixedCredentialsProvider.create(mockCreds)); - projectsClient = ProjectsClient.create(projectsSettingsBuilder.build()); - IAMSettings.Builder iamSettingsBuilder = IAMSettings.newBuilder() - .setCredentialsProvider(FixedCredentialsProvider.create(mockCreds)); - iamClient = IAMClient.create(iamSettingsBuilder.build()); - return new GcpIam.Builder() - .withProjectsClient(projectsClient) - .withIamClient(iamClient) - .build(); - } - } catch (IOException e) { - Assertions.fail("Failed to create GCP clients", e); - return null; - } - } - - @Override - public String getIdentityName() { - return "serviceAccount:chameleon@substrate-sdk-gcp-poc1.iam.gserviceaccount.com"; - } - - @Override - public String getTenantId() { - return "projects/substrate-sdk-gcp-poc1"; - } - - @Override - public String getRegion() { - return "us-west1"; - } - - @Override - public String getProviderId() { - return GcpConstants.PROVIDER_ID; - } - - @Override - public int getPort() { - return port; - } - - @Override - public List getWiremockExtensions() { - return List.of("com.salesforce.multicloudj.iam.gcp.util.IamJsonResponseTransformer"); - } - - @Override - public String getIamEndpoint() { - return "https://cloudresourcemanager.googleapis.com"; - } - - - @Override - public String getTestPolicyEffect() { - return "Allow"; - } - - @Override - public List getTestPolicyActions() { - return List.of("roles/storage.objectViewer", "roles/storage.objectCreator"); - } - - @Override - public String getTestPolicyName() { - return "roles/storage.objectViewer"; - } - - @Override - public void close() { - if (projectsClient != null) { - projectsClient.close(); - } - if (iamClient != null) { - iamClient.close(); - } - } - } -} + private static final Logger logger = LoggerFactory.getLogger(GcpIamIT.class); + private static WireMockServer grpcWireMockServer; + private static WireMockGrpcService mockIamService; + private static final String GRPC_DIR = "src/test/resources/grpc"; + private static final String RECORDINGS_DIR = "src/test/resources/recordings"; + + /** + * Sets up WireMock server with gRPC extension for replay mode. + * Synchronized to prevent race conditions if called from multiple test instances. + */ + private static synchronized void setupGrpcWireMock(int port) throws IOException { + // Double-check if already set up + if (grpcWireMockServer != null && grpcWireMockServer.isRunning()) { + return; + } + // 1. Prepare directories and clean up old descriptor files + Files.createDirectories(Paths.get(GRPC_DIR)); + + // Delete any existing descriptor files to avoid corruption + Path grpcDir = Paths.get(GRPC_DIR); + if (Files.exists(grpcDir)) { + Files.list(grpcDir) + .filter(path -> path.toString().endsWith(".dsc")) + .forEach(path -> { + try { + Files.delete(path); + logger.info("Deleted old descriptor: {}", path); + } catch (IOException e) { + logger.error("Failed to delete: {}", path, e); + } + }); + } + + // 2. Generate proto descriptor BEFORE starting WireMock + saveProtoDescriptor(); + + // 3. Start WireMock with gRPC Extension (plaintext) + WireMockConfiguration config = WireMockConfiguration.wireMockConfig() + .port(port) + .extensions(new GrpcExtensionFactory()); + + grpcWireMockServer = new WireMockServer(config); + grpcWireMockServer.start(); + + logger.info("gRPC WireMock started on port: {}", port); + + // 4. Initialize WireMockGrpcService for the IAM service + mockIamService = new WireMockGrpcService( + new WireMock(port), + "google.iam.admin.v1.IAM" + ); + logger.info("Initialized WireMockGrpcService for google.iam.admin.v1.IAM"); + + // 5. Load stubs from recorded JSON files + loadAllStubs(); + } + + /** + * Loads all stub files from the recordings directory. + */ + private static void loadAllStubs() throws IOException { + Path recordingsDir = Paths.get(RECORDINGS_DIR); + if (!Files.exists(recordingsDir)) { + logger.info("No recordings directory found: {}", recordingsDir); + logger.info("Run tests with -Drecord first to create recordings"); + return; + } + + int stubsLoaded = 0; + int stubsFailed = 0; + + // Load all JSON files as stubs + try (Stream paths = Files.walk(recordingsDir)) { + List jsonFiles = paths.filter(Files::isRegularFile) + .filter(path -> path.toString().endsWith(".json")) + .collect(java.util.stream.Collectors.toList()); + + logger.info("Found {} recording files", jsonFiles.size()); + + for (Path path : jsonFiles) { + try { + loadStubFromFile(path); + stubsLoaded++; + } catch (IOException e) { + stubsFailed++; + logger.error("Failed to load stub: {}", path, e); + } + } + } + + logger.info("Successfully loaded {} stubs, {} failed", stubsLoaded, stubsFailed); + logger.info("Total stub mappings in WireMock: {}", grpcWireMockServer.getStubMappings().size()); + } + + /** + * Loads a single stub file and registers it with WireMock. + * File naming convention: --.json + */ + private static void loadStubFromFile(Path filePath) throws IOException { + String fileName = filePath.getFileName().toString(); + + // Read the response JSON + String jsonContent = new String(Files.readAllBytes(filePath)); + + logger.info("Processing stub file: {}", fileName); + logger.info(" JSON content length: {} bytes", jsonContent.length()); + + JsonNode root = OBJECT_MAPPER.readTree(jsonContent); + String methodNameFromJson = root.path("methodName").asText(null); + JsonNode requestNode = root.path("request"); + JsonNode responseNode = root.path("response"); + + if (methodNameFromJson == null) { + logger.info("Skipping file with unexpected format: {}", fileName); + return; + } + + // Convert inner JSON nodes back to JSON strings + String requestJson = requestNode.isMissingNode() + ? null + : OBJECT_MAPPER.writeValueAsString(requestNode); + + String responseJson = responseNode.isMissingNode() + ? null + : OBJECT_MAPPER.writeValueAsString(responseNode); + + // Determine the response type based on method name and parse accordingly + Message responseMessage = parseResponseForMethod(methodNameFromJson, responseJson); + + if (responseMessage == null) { + logger.error(" ERROR: Unknown method type, skipping: {}", methodNameFromJson); + return; + } + + logger.info(" Parsed message type: {}", responseMessage.getClass().getSimpleName()); + // Register stub with WireMock + mockIamService.stubFor( + method(methodNameFromJson) + .withRequestMessage(equalToJson(requestJson)) + .willReturn(message(responseMessage)) + ); + + logger.info(" ✓ Successfully registered stub for: {}", methodNameFromJson); + } + + private static final ObjectMapper OBJECT_MAPPER = createObjectMapper(); + + private static ObjectMapper createObjectMapper() { + ObjectMapper mapper = new ObjectMapper(); + mapper.enable(SerializationFeature.INDENT_OUTPUT); + return mapper; + } + + /** + * Extracts the value for the given top-level key from a JSON string. + * + * @param jsonString JSON like: {"name":"foo", "projectId":"bar"} + * @param key key to extract, e.g. "name" + * @return value as String, or null if missing / parse error + */ + public static String extractValueForKey(String jsonString, String key) { + if (jsonString == null || key == null || key.isEmpty()) { + return null; + } + try { + JsonNode root = OBJECT_MAPPER.readTree(jsonString); + JsonNode node = root.get(key); + return (node != null && !node.isNull()) ? node.asText() : null; + } catch (Exception e) { + return null; + } + } + + /** + * Extracts the service account name (local-part) from a full GCP service account resource name. + * + * Example: + * input: "projects/substrate-sdk-gcp-poc1/serviceAccounts/test-sa-get@substrate-sdk-gcp-poc1.iam.gserviceaccount.com" + * output: "test-sa-get" + * + * @param fullName full resource name or email + * @return service account name, or null if it cannot be extracted + */ + private static String extractServiceAccountIdFromFullName(String fullName) { + if (fullName == null || fullName.isEmpty()) { + return ""; + } + + // Last path segment: could be "test-sa-get@project.iam.gserviceaccount.com" + String lastSegment = fullName; + int slashIndex = fullName.lastIndexOf('/'); + if (slashIndex >= 0 && slashIndex + 1 < fullName.length()) { + lastSegment = fullName.substring(slashIndex + 1); + } + + // Take part before '@' if it's an email + int atIndex = lastSegment.indexOf('@'); + if (atIndex > 0) { + return lastSegment.substring(0, atIndex); + } + + // If there's no '@', assume the whole last segment is the name + return lastSegment; + } + + /** + * Parses response JSON into the appropriate protobuf message type. + */ + private static Message parseResponseForMethod(String methodName, String jsonContent) throws IOException { + try { + switch (methodName) { + case "CreateServiceAccount": + case "GetServiceAccount": + ServiceAccount.Builder saBuilder = ServiceAccount.newBuilder(); + JsonFormat.parser().merge(jsonContent, saBuilder); + return saBuilder.build(); + + case "DeleteServiceAccount": + // DeleteServiceAccount returns Empty + Empty.Builder emptyBuilder = Empty.newBuilder(); + JsonFormat.parser().merge(jsonContent, emptyBuilder); + return emptyBuilder.build(); + + case "GetIamPolicy": + case "SetIamPolicy": + // IAM Policy methods return Policy + Policy.Builder policyBuilder = Policy.newBuilder(); + JsonFormat.parser().merge(jsonContent, policyBuilder); + return policyBuilder.build(); + + default: + logger.warn("Unknown method type: {}", methodName); + return null; + } + } catch (Exception e) { + throw new IOException("Failed to parse response for " + methodName, e); + } + } + + /** + * Saves the proto descriptor file that WireMock needs to parse gRPC requests. + */ + private static void saveProtoDescriptor() throws IOException { + Descriptors.FileDescriptor fileDescriptor = ServiceAccount.getDescriptor().getFile(); + + DescriptorProtos.FileDescriptorSet.Builder descriptorSetBuilder = + DescriptorProtos.FileDescriptorSet.newBuilder(); + + Set addedFiles = new HashSet<>(); + addFileDescriptorWithDependencies(fileDescriptor, descriptorSetBuilder, addedFiles); + + DescriptorProtos.FileDescriptorSet descriptorSet = descriptorSetBuilder.build(); + + Path descriptorPath = Paths.get(GRPC_DIR, "iam_admin.dsc"); + Files.write(descriptorPath, descriptorSet.toByteArray()); + + logger.info("Proto descriptor saved: {}", descriptorPath.toAbsolutePath()); + logger.info("Descriptor contains {} files", descriptorSet.getFileCount()); + } + + /** + * Recursively adds file descriptor and dependencies. + */ + private static void addFileDescriptorWithDependencies( + Descriptors.FileDescriptor fileDescriptor, + DescriptorProtos.FileDescriptorSet.Builder builder, + Set addedFiles) { + + String fileName = fileDescriptor.getName(); + if (addedFiles.contains(fileName)) { + return; + } + + for (Descriptors.FileDescriptor dependency : fileDescriptor.getDependencies()) { + addFileDescriptorWithDependencies(dependency, builder, addedFiles); + } + + builder.addFile(fileDescriptor.toProto()); + addedFiles.add(fileName); + } + + @Override + protected Harness createHarness() { + return new HarnessImpl(); + } + + /** + * Harness implementation for GCP IAM integration tests with gRPC WireMock support. + */ + public static class HarnessImpl implements AbstractIamIT.Harness { + IAMClient iamClient; + ProjectsClient projectsClient; + // Parent starts HTTP WireMock on this port, we use a different port for gRPC + int port = ThreadLocalRandom.current().nextInt(1000, 10000); + int grpcPort = ThreadLocalRandom.current().nextInt(10000, 20000); + + private static final String TEST_PROJECT_ID = "projects/substrate-sdk-gcp-poc1"; + private static final String TEST_REGION = "us-west1"; + private static final String TEST_IDENTITY_NAME = "testSa"; + private static final String IAM_ENDPOINT = "https://cloudresourcemanager.googleapis.com"; + private static final String TRUSTED_PRINCIPAL = "chameleon@substrate-sdk-gcp-poc1.iam.gserviceaccount.com"; + + @Override + public AbstractIam createIamDriver(boolean useValidCredentials) { + boolean isRecordMode = System.getProperty("record") != null; + TransportChannelProvider channelProvider = TestsUtilGcp.getTransportChannelProvider(port); + ProjectsSettings.Builder projectsSettingsBuilder = ProjectsSettings.newBuilder() + .setTransportChannelProvider(channelProvider); + try { + if (isRecordMode && useValidCredentials) { + // RECORD mode: Use gRPC interceptor to capture and save responses + logger.info("--- RECORD MODE: Connecting to Real GCP with Recording ---"); + Files.createDirectories(Paths.get(RECORDINGS_DIR)); + + // Create IAM client with recording interceptor + RecordingInterceptor recordingInterceptor = new RecordingInterceptor(); + + IAMStubSettings.Builder stubSettingsBuilder = IAMStubSettings.newBuilder(); + stubSettingsBuilder.setTransportChannelProvider( + InstantiatingGrpcChannelProvider.newBuilder() + .setInterceptorProvider(() -> List.of(recordingInterceptor)) + .build() + ); + + iamClient = IAMClient.create(IAMSettings.create(stubSettingsBuilder.build())); + projectsClient = ProjectsClient.create(projectsSettingsBuilder.build()); + return new GcpIam.Builder() + .withProjectsClient(projectsClient) + .withIamClient(iamClient) + .build(); + } else { + // REPLAY mode: Setup gRPC WireMock if not already done, then connect + logger.info("--- REPLAY MODE: Connecting to WireMock gRPC ---"); + + // Lazy initialization of gRPC WireMock + if (grpcWireMockServer == null || !grpcWireMockServer.isRunning()) { + setupGrpcWireMock(grpcPort); + } + + IAMStubSettings.Builder stubSettingsBuilder = IAMStubSettings.newBuilder(); + stubSettingsBuilder.setEndpoint("localhost:" + grpcPort); + stubSettingsBuilder.setCredentialsProvider(NoCredentialsProvider.create()); + + // Use plaintext for WireMock gRPC connections + stubSettingsBuilder.setTransportChannelProvider( + InstantiatingGrpcChannelProvider.newBuilder() + .setChannelConfigurator(io.grpc.ManagedChannelBuilder::usePlaintext) + .build() + ); + + iamClient = IAMClient.create(IAMSettings.create(stubSettingsBuilder.build())); + GoogleCredentials mockCreds = MockGoogleCredentialsFactory.createMockCredentials(); + projectsSettingsBuilder.setCredentialsProvider(FixedCredentialsProvider.create(mockCreds)); + projectsClient = ProjectsClient.create(projectsSettingsBuilder.build()); + return new GcpIam.Builder() + .withProjectsClient(projectsClient) + .withIamClient(iamClient) + .build(); + } + } catch (IOException e) { + Assertions.fail("Failed to create IAM client", e); + return null; + } + } + + @Override + public String getTenantId() { + return TEST_PROJECT_ID; + } + + @Override + public String getRegion() { + return TEST_REGION; + } + + @Override + public String getTestIdentityName() { + return TEST_IDENTITY_NAME; + } + + @Override + public String getIamEndpoint() { + return IAM_ENDPOINT; + } + + @Override + public String getProviderId() { + return GcpConstants.PROVIDER_ID; + } + + @Override + public int getPort() { + // Port is used by parent class to start WireMock server + // for HTTP-based policy API recording via WireMock proxy. + return port; + } + + @Override + public List getWiremockExtensions() { + // HTTP-based policy APIs use JSON transformer for record/replay. + return List.of("com.salesforce.multicloudj.iam.gcp.util.IamJsonResponseTransformer"); + } + + @Override + public String getTrustedPrincipal() { + return TRUSTED_PRINCIPAL; + } + + @Override + public String getIdentityName() { + return "serviceAccount:chameleon@substrate-sdk-gcp-poc1.iam.gserviceaccount.com"; + } + + @Override + public String getTestPolicyEffect() { + return "Allow"; + } + + @Override + public List getTestPolicyActions() { + return List.of("roles/storage.objectViewer", "roles/storage.objectCreator"); + } + + @Override + public String getTestPolicyName() { + return "roles/storage.objectViewer"; + } + + @Override + public void close() { + if (iamClient != null) { + iamClient.close(); + } + if (projectsClient != null) { + projectsClient.close(); + } + // Clean up gRPC WireMock server if it's running + if (grpcWireMockServer != null && grpcWireMockServer.isRunning()) { + grpcWireMockServer.stop(); + logger.info("Stopped gRPC WireMock server"); + } + } + } + + /** + * gRPC ClientInterceptor that records responses to JSON files. + * This enables the record/replay pattern for gRPC APIs. + */ + static class RecordingInterceptor implements ClientInterceptor { + private int callCounter = 0; + + @Override + public ClientCall interceptCall( + MethodDescriptor method, + CallOptions callOptions, + Channel next) { + // 1. We hold the request message here so the listener can access it later + AtomicReference requestCapture = new AtomicReference<>(); + + return new ForwardingClientCall.SimpleForwardingClientCall( + next.newCall(method, callOptions)) { + + @Override + public void sendMessage(ReqT message) { + // Capture the outgoing request + requestCapture.set(message); + super.sendMessage(message); + } + + @Override + public void start(Listener responseListener, Metadata headers) { + // Wrap the listener to intercept responses + Listener recordingListener = new ForwardingClientCallListener.SimpleForwardingClientCallListener(responseListener) { + + @Override + public void onMessage(RespT message) { + // Save the response + saveRequestResponse(method.getFullMethodName(), (Message)(requestCapture.get()), (Message) message); + super.onMessage(message); + } + }; + + super.start(recordingListener, headers); + } + }; + } + + /** + * Saves a gRPC response to a JSON file. + * Method name format: google.iam.admin.v1.IAM/CreateServiceAccount + */ + private void saveRequestResponse(String fullMethodName, Message request, Message response) { + try { + // Extract method name from full path (e.g., "CreateServiceAccount" from "google.iam.admin.v1.IAM/CreateServiceAccount") + String methodName = fullMethodName.substring(fullMethodName.lastIndexOf('/') + 1); + + String responseJson = JsonFormat.printer().print(response); + String requestJson = JsonFormat.printer().print(request); + + // Parse the request/response strings into JSON trees + JsonNode requestNode = OBJECT_MAPPER.readTree(requestJson); + JsonNode responseNode = OBJECT_MAPPER.readTree(responseJson); + + // Build the wrapper object + ObjectNode root = OBJECT_MAPPER.createObjectNode(); + root.put("methodName", methodName); + root.set("request", requestNode); + root.set("response", responseNode); + + // Serialize back to a JSON string + String requestResponseJson = OBJECT_MAPPER.writeValueAsString(root); + + String serviceAccountId = extractServiceAccountId(methodName, requestJson, responseJson); + String filename; + if (!serviceAccountId.isEmpty()) { + filename = String.format("%s-%s-%04d.json", methodName, serviceAccountId, callCounter++); + } else { + filename = String.format("%s-%04d.json", methodName, callCounter++); + } + Path filePath = Paths.get(RECORDINGS_DIR, filename); + Files.write(filePath, requestResponseJson.getBytes()); + logger.info("Recorded: {}", filename); + } catch (IOException e) { + logger.error("Failed to record response for {}", fullMethodName, e); + } + } + + private String extractServiceAccountId(String methodName, String requestJson, String responseJson) { + + switch (methodName) { + case "CreateServiceAccount": + return extractValueForKey(requestJson, "accountId"); + case "DeleteServiceAccount": + case "GetServiceAccount": + return extractServiceAccountIdFromFullName(extractValueForKey(requestJson, "name")); + case "SetIamPolicy": + case "GetIamPolicy": + return extractServiceAccountIdFromFullName(extractValueForKey(requestJson, "resource")); + default: + throw new IllegalArgumentException("Unknown method: " + methodName); + } + } + } +} diff --git a/iam/iam-gcp/src/test/resources/grpc/iam_admin.dsc b/iam/iam-gcp/src/test/resources/grpc/iam_admin.dsc new file mode 100644 index 0000000000000000000000000000000000000000..8478928fe5dc9d8ab1c9c87267e93e06bbbeb6a0 GIT binary patch literal 33982 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 HE=~VGHu$8K literal 0 HcmV?d00001 diff --git a/iam/iam-gcp/src/test/resources/recordings/CreateServiceAccount-testSa-0000.json b/iam/iam-gcp/src/test/resources/recordings/CreateServiceAccount-testSa-0000.json new file mode 100644 index 000000000..7ae776d9e --- /dev/null +++ b/iam/iam-gcp/src/test/resources/recordings/CreateServiceAccount-testSa-0000.json @@ -0,0 +1,21 @@ +{ + "methodName" : "CreateServiceAccount", + "request" : { + "name" : "projects/substrate-sdk-gcp-poc1", + "accountId" : "testSa", + "serviceAccount" : { + "displayName" : "testSa", + "description" : "Test identity for MultiCloudJ integration tests" + } + }, + "response" : { + "name" : "projects/substrate-sdk-gcp-poc1/serviceAccounts/testSa@substrate-sdk-gcp-poc1.iam.gserviceaccount.com", + "projectId" : "substrate-sdk-gcp-poc1", + "uniqueId" : "102748697421896258849", + "email" : "testSa@substrate-sdk-gcp-poc1.iam.gserviceaccount.com", + "displayName" : "testSa", + "etag" : "MDEwMjE5MjA=", + "description" : "Test identity for MultiCloudJ integration tests", + "oauth2ClientId" : "102748697421896258849" + } +} \ No newline at end of file diff --git a/iam/iam-gcp/src/test/resources/recordings/CreateServiceAccount-testSaDelete-0000.json b/iam/iam-gcp/src/test/resources/recordings/CreateServiceAccount-testSaDelete-0000.json new file mode 100644 index 000000000..306ae7efb --- /dev/null +++ b/iam/iam-gcp/src/test/resources/recordings/CreateServiceAccount-testSaDelete-0000.json @@ -0,0 +1,21 @@ +{ + "methodName" : "CreateServiceAccount", + "request" : { + "name" : "projects/substrate-sdk-gcp-poc1", + "accountId" : "testSaDelete", + "serviceAccount" : { + "displayName" : "testSaDelete", + "description" : "Test identity for delete operation" + } + }, + "response" : { + "name" : "projects/substrate-sdk-gcp-poc1/serviceAccounts/testSaDelete@substrate-sdk-gcp-poc1.iam.gserviceaccount.com", + "projectId" : "substrate-sdk-gcp-poc1", + "uniqueId" : "114427331964098776950", + "email" : "testSaDelete@substrate-sdk-gcp-poc1.iam.gserviceaccount.com", + "displayName" : "testSaDelete", + "etag" : "MDEwMjE5MjA=", + "description" : "Test identity for delete operation", + "oauth2ClientId" : "114427331964098776950" + } +} \ No newline at end of file diff --git a/iam/iam-gcp/src/test/resources/recordings/CreateServiceAccount-testSaGet-0000.json b/iam/iam-gcp/src/test/resources/recordings/CreateServiceAccount-testSaGet-0000.json new file mode 100644 index 000000000..a85ce2784 --- /dev/null +++ b/iam/iam-gcp/src/test/resources/recordings/CreateServiceAccount-testSaGet-0000.json @@ -0,0 +1,21 @@ +{ + "methodName" : "CreateServiceAccount", + "request" : { + "name" : "projects/substrate-sdk-gcp-poc1", + "accountId" : "testSaGet", + "serviceAccount" : { + "displayName" : "testSaGet", + "description" : "Test identity for get operation" + } + }, + "response" : { + "name" : "projects/substrate-sdk-gcp-poc1/serviceAccounts/testSaGet@substrate-sdk-gcp-poc1.iam.gserviceaccount.com", + "projectId" : "substrate-sdk-gcp-poc1", + "uniqueId" : "107637792717662016720", + "email" : "testSaGet@substrate-sdk-gcp-poc1.iam.gserviceaccount.com", + "displayName" : "testSaGet", + "etag" : "MDEwMjE5MjA=", + "description" : "Test identity for get operation", + "oauth2ClientId" : "107637792717662016720" + } +} \ No newline at end of file diff --git a/iam/iam-gcp/src/test/resources/recordings/CreateServiceAccount-testSaLifeCycle-0000.json b/iam/iam-gcp/src/test/resources/recordings/CreateServiceAccount-testSaLifeCycle-0000.json new file mode 100644 index 000000000..4e526c483 --- /dev/null +++ b/iam/iam-gcp/src/test/resources/recordings/CreateServiceAccount-testSaLifeCycle-0000.json @@ -0,0 +1,21 @@ +{ + "methodName" : "CreateServiceAccount", + "request" : { + "name" : "projects/substrate-sdk-gcp-poc1", + "accountId" : "testSaLifeCycle", + "serviceAccount" : { + "displayName" : "testSaLifeCycle", + "description" : "Test identity for lifecycle test" + } + }, + "response" : { + "name" : "projects/substrate-sdk-gcp-poc1/serviceAccounts/testSaLifeCycle@substrate-sdk-gcp-poc1.iam.gserviceaccount.com", + "projectId" : "substrate-sdk-gcp-poc1", + "uniqueId" : "114276556953663474442", + "email" : "testSaLifeCycle@substrate-sdk-gcp-poc1.iam.gserviceaccount.com", + "displayName" : "testSaLifeCycle", + "etag" : "MDEwMjE5MjA=", + "description" : "Test identity for lifecycle test", + "oauth2ClientId" : "114276556953663474442" + } +} \ No newline at end of file diff --git a/iam/iam-gcp/src/test/resources/recordings/CreateServiceAccount-testSaNoDesc-0000.json b/iam/iam-gcp/src/test/resources/recordings/CreateServiceAccount-testSaNoDesc-0000.json new file mode 100644 index 000000000..45c8f5911 --- /dev/null +++ b/iam/iam-gcp/src/test/resources/recordings/CreateServiceAccount-testSaNoDesc-0000.json @@ -0,0 +1,19 @@ +{ + "methodName" : "CreateServiceAccount", + "request" : { + "name" : "projects/substrate-sdk-gcp-poc1", + "accountId" : "testSaNoDesc", + "serviceAccount" : { + "displayName" : "testSaNoDesc" + } + }, + "response" : { + "name" : "projects/substrate-sdk-gcp-poc1/serviceAccounts/testSaNoDesc@substrate-sdk-gcp-poc1.iam.gserviceaccount.com", + "projectId" : "substrate-sdk-gcp-poc1", + "uniqueId" : "102855534879409764604", + "email" : "testSaNoDesc@substrate-sdk-gcp-poc1.iam.gserviceaccount.com", + "displayName" : "testSaNoDesc", + "etag" : "MDEwMjE5MjA=", + "oauth2ClientId" : "102855534879409764604" + } +} \ No newline at end of file diff --git a/iam/iam-gcp/src/test/resources/recordings/CreateServiceAccount-testSaOptions-0000.json b/iam/iam-gcp/src/test/resources/recordings/CreateServiceAccount-testSaOptions-0000.json new file mode 100644 index 000000000..a080d60a0 --- /dev/null +++ b/iam/iam-gcp/src/test/resources/recordings/CreateServiceAccount-testSaOptions-0000.json @@ -0,0 +1,21 @@ +{ + "methodName" : "CreateServiceAccount", + "request" : { + "name" : "projects/substrate-sdk-gcp-poc1", + "accountId" : "testSaOptions", + "serviceAccount" : { + "displayName" : "testSaOptions", + "description" : "Test identity with options" + } + }, + "response" : { + "name" : "projects/substrate-sdk-gcp-poc1/serviceAccounts/testSaOptions@substrate-sdk-gcp-poc1.iam.gserviceaccount.com", + "projectId" : "substrate-sdk-gcp-poc1", + "uniqueId" : "100474553911983032550", + "email" : "testSaOptions@substrate-sdk-gcp-poc1.iam.gserviceaccount.com", + "displayName" : "testSaOptions", + "etag" : "MDEwMjE5MjA=", + "description" : "Test identity with options", + "oauth2ClientId" : "100474553911983032550" + } +} \ No newline at end of file diff --git a/iam/iam-gcp/src/test/resources/recordings/CreateServiceAccount-testSaTrusted-0000.json b/iam/iam-gcp/src/test/resources/recordings/CreateServiceAccount-testSaTrusted-0000.json new file mode 100644 index 000000000..9deae40d0 --- /dev/null +++ b/iam/iam-gcp/src/test/resources/recordings/CreateServiceAccount-testSaTrusted-0000.json @@ -0,0 +1,21 @@ +{ + "methodName" : "CreateServiceAccount", + "request" : { + "name" : "projects/substrate-sdk-gcp-poc1", + "accountId" : "testSaTrusted", + "serviceAccount" : { + "displayName" : "testSaTrusted", + "description" : "Test identity with trust configuration" + } + }, + "response" : { + "name" : "projects/substrate-sdk-gcp-poc1/serviceAccounts/testSaTrusted@substrate-sdk-gcp-poc1.iam.gserviceaccount.com", + "projectId" : "substrate-sdk-gcp-poc1", + "uniqueId" : "114349486479818249800", + "email" : "testSaTrusted@substrate-sdk-gcp-poc1.iam.gserviceaccount.com", + "displayName" : "testSaTrusted", + "etag" : "MDEwMjE5MjA=", + "description" : "Test identity with trust configuration", + "oauth2ClientId" : "114349486479818249800" + } +} \ No newline at end of file diff --git a/iam/iam-gcp/src/test/resources/recordings/DeleteServiceAccount-testSa-0001.json b/iam/iam-gcp/src/test/resources/recordings/DeleteServiceAccount-testSa-0001.json new file mode 100644 index 000000000..c85cd4a2c --- /dev/null +++ b/iam/iam-gcp/src/test/resources/recordings/DeleteServiceAccount-testSa-0001.json @@ -0,0 +1,7 @@ +{ + "methodName" : "DeleteServiceAccount", + "request" : { + "name" : "projects/substrate-sdk-gcp-poc1/serviceAccounts/testSa@substrate-sdk-gcp-poc1.iam.gserviceaccount.com" + }, + "response" : { } +} \ No newline at end of file diff --git a/iam/iam-gcp/src/test/resources/recordings/DeleteServiceAccount-testSaDelete-0001.json b/iam/iam-gcp/src/test/resources/recordings/DeleteServiceAccount-testSaDelete-0001.json new file mode 100644 index 000000000..af6005dda --- /dev/null +++ b/iam/iam-gcp/src/test/resources/recordings/DeleteServiceAccount-testSaDelete-0001.json @@ -0,0 +1,7 @@ +{ + "methodName" : "DeleteServiceAccount", + "request" : { + "name" : "projects/substrate-sdk-gcp-poc1/serviceAccounts/testSaDelete@substrate-sdk-gcp-poc1.iam.gserviceaccount.com" + }, + "response" : { } +} \ No newline at end of file diff --git a/iam/iam-gcp/src/test/resources/recordings/DeleteServiceAccount-testSaGet-0002.json b/iam/iam-gcp/src/test/resources/recordings/DeleteServiceAccount-testSaGet-0002.json new file mode 100644 index 000000000..2fbadbdb8 --- /dev/null +++ b/iam/iam-gcp/src/test/resources/recordings/DeleteServiceAccount-testSaGet-0002.json @@ -0,0 +1,7 @@ +{ + "methodName" : "DeleteServiceAccount", + "request" : { + "name" : "projects/substrate-sdk-gcp-poc1/serviceAccounts/testSaGet@substrate-sdk-gcp-poc1.iam.gserviceaccount.com" + }, + "response" : { } +} \ No newline at end of file diff --git a/iam/iam-gcp/src/test/resources/recordings/DeleteServiceAccount-testSaLifeCycle-0002.json b/iam/iam-gcp/src/test/resources/recordings/DeleteServiceAccount-testSaLifeCycle-0002.json new file mode 100644 index 000000000..c657cb3d1 --- /dev/null +++ b/iam/iam-gcp/src/test/resources/recordings/DeleteServiceAccount-testSaLifeCycle-0002.json @@ -0,0 +1,7 @@ +{ + "methodName" : "DeleteServiceAccount", + "request" : { + "name" : "projects/substrate-sdk-gcp-poc1/serviceAccounts/testSaLifeCycle@substrate-sdk-gcp-poc1.iam.gserviceaccount.com" + }, + "response" : { } +} \ No newline at end of file diff --git a/iam/iam-gcp/src/test/resources/recordings/DeleteServiceAccount-testSaNoDesc-0001.json b/iam/iam-gcp/src/test/resources/recordings/DeleteServiceAccount-testSaNoDesc-0001.json new file mode 100644 index 000000000..0736a750f --- /dev/null +++ b/iam/iam-gcp/src/test/resources/recordings/DeleteServiceAccount-testSaNoDesc-0001.json @@ -0,0 +1,7 @@ +{ + "methodName" : "DeleteServiceAccount", + "request" : { + "name" : "projects/substrate-sdk-gcp-poc1/serviceAccounts/testSaNoDesc@substrate-sdk-gcp-poc1.iam.gserviceaccount.com" + }, + "response" : { } +} \ No newline at end of file diff --git a/iam/iam-gcp/src/test/resources/recordings/DeleteServiceAccount-testSaOptions-0001.json b/iam/iam-gcp/src/test/resources/recordings/DeleteServiceAccount-testSaOptions-0001.json new file mode 100644 index 000000000..737ecb44a --- /dev/null +++ b/iam/iam-gcp/src/test/resources/recordings/DeleteServiceAccount-testSaOptions-0001.json @@ -0,0 +1,7 @@ +{ + "methodName" : "DeleteServiceAccount", + "request" : { + "name" : "projects/substrate-sdk-gcp-poc1/serviceAccounts/testSaOptions@substrate-sdk-gcp-poc1.iam.gserviceaccount.com" + }, + "response" : { } +} \ No newline at end of file diff --git a/iam/iam-gcp/src/test/resources/recordings/DeleteServiceAccount-testSaTrusted-0003.json b/iam/iam-gcp/src/test/resources/recordings/DeleteServiceAccount-testSaTrusted-0003.json new file mode 100644 index 000000000..e6a0a5718 --- /dev/null +++ b/iam/iam-gcp/src/test/resources/recordings/DeleteServiceAccount-testSaTrusted-0003.json @@ -0,0 +1,7 @@ +{ + "methodName" : "DeleteServiceAccount", + "request" : { + "name" : "projects/substrate-sdk-gcp-poc1/serviceAccounts/testSaTrusted@substrate-sdk-gcp-poc1.iam.gserviceaccount.com" + }, + "response" : { } +} \ No newline at end of file diff --git a/iam/iam-gcp/src/test/resources/recordings/GetIamPolicy-testSaTrusted-0001.json b/iam/iam-gcp/src/test/resources/recordings/GetIamPolicy-testSaTrusted-0001.json new file mode 100644 index 000000000..457e8129a --- /dev/null +++ b/iam/iam-gcp/src/test/resources/recordings/GetIamPolicy-testSaTrusted-0001.json @@ -0,0 +1,9 @@ +{ + "methodName" : "GetIamPolicy", + "request" : { + "resource" : "projects/substrate-sdk-gcp-poc1/serviceAccounts/testSaTrusted@substrate-sdk-gcp-poc1.iam.gserviceaccount.com" + }, + "response" : { + "etag" : "ACAB" + } +} \ No newline at end of file diff --git a/iam/iam-gcp/src/test/resources/recordings/GetServiceAccount-testSaGet-0001.json b/iam/iam-gcp/src/test/resources/recordings/GetServiceAccount-testSaGet-0001.json new file mode 100644 index 000000000..7e947ca57 --- /dev/null +++ b/iam/iam-gcp/src/test/resources/recordings/GetServiceAccount-testSaGet-0001.json @@ -0,0 +1,16 @@ +{ + "methodName" : "GetServiceAccount", + "request" : { + "name" : "projects/substrate-sdk-gcp-poc1/serviceAccounts/testSaGet@substrate-sdk-gcp-poc1.iam.gserviceaccount.com" + }, + "response" : { + "name" : "projects/substrate-sdk-gcp-poc1/serviceAccounts/testSaGet@substrate-sdk-gcp-poc1.iam.gserviceaccount.com", + "projectId" : "substrate-sdk-gcp-poc1", + "uniqueId" : "107637792717662016720", + "email" : "testSaGet@substrate-sdk-gcp-poc1.iam.gserviceaccount.com", + "displayName" : "testSaGet", + "etag" : "MDEwMjE5MjA=", + "description" : "Test identity for get operation", + "oauth2ClientId" : "107637792717662016720" + } +} \ No newline at end of file diff --git a/iam/iam-gcp/src/test/resources/recordings/GetServiceAccount-testSaLifeCycle-0000.json b/iam/iam-gcp/src/test/resources/recordings/GetServiceAccount-testSaLifeCycle-0000.json new file mode 100644 index 000000000..f67488b28 --- /dev/null +++ b/iam/iam-gcp/src/test/resources/recordings/GetServiceAccount-testSaLifeCycle-0000.json @@ -0,0 +1,16 @@ +{ + "methodName" : "GetServiceAccount", + "request" : { + "name" : "projects/substrate-sdk-gcp-poc1/serviceAccounts/testSaLifeCycle@substrate-sdk-gcp-poc1.iam.gserviceaccount.com" + }, + "response" : { + "name" : "projects/substrate-sdk-gcp-poc1/serviceAccounts/testSaLifeCycle@substrate-sdk-gcp-poc1.iam.gserviceaccount.com", + "projectId" : "substrate-sdk-gcp-poc1", + "uniqueId" : "112054972166957401551", + "email" : "testSaLifeCycle@substrate-sdk-gcp-poc1.iam.gserviceaccount.com", + "displayName" : "testSaLifeCycle", + "etag" : "MDEwMjE5MjA=", + "description" : "Test identity for lifecycle test", + "oauth2ClientId" : "112054972166957401551" + } +} \ No newline at end of file diff --git a/iam/iam-gcp/src/test/resources/recordings/GetServiceAccount-testSaLifeCycle-0001.json b/iam/iam-gcp/src/test/resources/recordings/GetServiceAccount-testSaLifeCycle-0001.json new file mode 100644 index 000000000..a2db6597e --- /dev/null +++ b/iam/iam-gcp/src/test/resources/recordings/GetServiceAccount-testSaLifeCycle-0001.json @@ -0,0 +1,16 @@ +{ + "methodName" : "GetServiceAccount", + "request" : { + "name" : "projects/substrate-sdk-gcp-poc1/serviceAccounts/testSaLifeCycle@substrate-sdk-gcp-poc1.iam.gserviceaccount.com" + }, + "response" : { + "name" : "projects/substrate-sdk-gcp-poc1/serviceAccounts/testSaLifeCycle@substrate-sdk-gcp-poc1.iam.gserviceaccount.com", + "projectId" : "substrate-sdk-gcp-poc1", + "uniqueId" : "114276556953663474442", + "email" : "testSaLifeCycle@substrate-sdk-gcp-poc1.iam.gserviceaccount.com", + "displayName" : "testSaLifeCycle", + "etag" : "MDEwMjE5MjA=", + "description" : "Test identity for lifecycle test", + "oauth2ClientId" : "114276556953663474442" + } +} \ No newline at end of file diff --git a/iam/iam-gcp/src/test/resources/recordings/SetIamPolicy-testSaTrusted-0002.json b/iam/iam-gcp/src/test/resources/recordings/SetIamPolicy-testSaTrusted-0002.json new file mode 100644 index 000000000..1c47bd4ed --- /dev/null +++ b/iam/iam-gcp/src/test/resources/recordings/SetIamPolicy-testSaTrusted-0002.json @@ -0,0 +1,21 @@ +{ + "methodName" : "SetIamPolicy", + "request" : { + "resource" : "projects/substrate-sdk-gcp-poc1/serviceAccounts/testSaTrusted@substrate-sdk-gcp-poc1.iam.gserviceaccount.com", + "policy" : { + "etag" : "ACAB", + "bindings" : [ { + "role" : "roles/iam.serviceAccountTokenCreator", + "members" : [ "serviceAccount:chameleon@substrate-sdk-gcp-poc1.iam.gserviceaccount.com" ] + } ] + } + }, + "response" : { + "version" : 1, + "etag" : "BwZGEqqsKlI=", + "bindings" : [ { + "role" : "roles/iam.serviceAccountTokenCreator", + "members" : [ "serviceAccount:chameleon@substrate-sdk-gcp-poc1.iam.gserviceaccount.com" ] + } ] + } +} \ No newline at end of file diff --git a/multicloudj-common/src/test/java/com/salesforce/multicloudj/common/util/common/TestsUtil.java b/multicloudj-common/src/test/java/com/salesforce/multicloudj/common/util/common/TestsUtil.java index 8e3a638f2..9c8b7eaab 100644 --- a/multicloudj-common/src/test/java/com/salesforce/multicloudj/common/util/common/TestsUtil.java +++ b/multicloudj-common/src/test/java/com/salesforce/multicloudj/common/util/common/TestsUtil.java @@ -22,6 +22,7 @@ import java.util.Base64; import java.util.List; import java.util.Map; +import java.util.regex.Pattern; import static com.github.tomakehurst.wiremock.client.WireMock.recordSpec; import static com.salesforce.multicloudj.common.util.common.TestsUtil.TruncateRequestBodyTransformer.TRUNCATE_MATCHER_REQUST_BODY_OVER; @@ -48,9 +49,12 @@ public StubMapping transform(StubMapping stubMapping, FileSource files, Paramete // See if any of the existing body patterns exceed our length limit for(ContentPattern pattern : bodyPatterns) { if(pattern.getExpected().length() > truncateMatcherRequestBodyOver){ - // We've exceeded our desired matcher length, so truncate it + // We've exceeded our desired matcher length, so truncate it. + // The truncated substring may start with regex metacharacters like '{', + // so we must escape it before constructing a RegexPattern. String truncatedString = pattern.getExpected().substring(0, truncateMatcherRequestBodyOver); - newPatterns.add(new RegexPattern("^" + truncatedString +"*")); + String escaped = Pattern.quote(truncatedString); + newPatterns.add(new RegexPattern("^" + escaped + ".*")); } }