This is an automated email from the ASF dual-hosted git repository.

adamsaghy pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/fineract.git


The following commit(s) were added to refs/heads/develop by this push:
     new c0f22f0bd9 FINERACT-248: Prevent duplicate SMS campaign names
c0f22f0bd9 is described below

commit c0f22f0bd9c70a2b1ec55ed78885eb817d2ec4f5
Author: Nick Iusiumbeli <[email protected]>
AuthorDate: Tue Feb 24 15:11:48 2026 +0200

    FINERACT-248: Prevent duplicate SMS campaign names
    
    Add validation to check for duplicate campaign names before creating or
    updating SMS campaigns. Also add a Liquibase migration to create the
    campaign_name_UNIQUE constraint on sms_campaign table, since the
    @UniqueConstraint annotation on the entity was just Hibernate metadata
    with no actual DB constraint behind it.
---
 .../test/stepdef/loan/LoanRescheduleStepDef.java   |  11 ++
 .../sms/domain/SmsCampaignRepository.java          |   4 +
 .../SmsCampaignNameAlreadyExistsException.java     |  28 +++++
 .../SmsCampaignWritePlatformServiceJpaImpl.java    |  50 ++++++---
 .../db/changelog/tenant/changelog-tenant.xml       |   1 +
 ...216_add_unique_constraint_sms_campaign_name.xml |  29 ++++++
 .../GroupSavingsIntegrationTest.java               |  13 +--
 .../SmsCampaignIntegrationTest.java                | 115 +++++++++++++++++++++
 .../common/organisation/CampaignsHelper.java       |  21 +++-
 9 files changed, 252 insertions(+), 20 deletions(-)

diff --git 
a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanRescheduleStepDef.java
 
b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanRescheduleStepDef.java
index e20d0bf3c8..2f982a965e 100644
--- 
a/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanRescheduleStepDef.java
+++ 
b/fineract-e2e-tests-core/src/test/java/org/apache/fineract/test/stepdef/loan/LoanRescheduleStepDef.java
@@ -42,6 +42,8 @@ import org.apache.fineract.client.models.PostLoansResponse;
 import org.apache.fineract.client.models.PostUpdateRescheduleLoansRequest;
 import org.apache.fineract.test.data.LoanRescheduleErrorMessage;
 import org.apache.fineract.test.helper.ErrorMessageHelper;
+import org.apache.fineract.test.messaging.event.EventCheckHelper;
+import org.apache.fineract.test.messaging.store.EventStore;
 import org.apache.fineract.test.stepdef.AbstractStepDef;
 import org.apache.fineract.test.support.TestContextKey;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -56,9 +58,14 @@ public class LoanRescheduleStepDef extends AbstractStepDef {
 
     @Autowired
     private FineractFeignClient fineractClient;
+    @Autowired
+    private EventStore eventStore;
+    @Autowired
+    private EventCheckHelper eventCheckHelper;
 
     @When("Admin creates and approves Loan reschedule with the following 
data:")
     public void createAndApproveLoanReschedule(DataTable table) throws 
IOException {
+        eventStore.reset();
         PostLoansResponse loanResponse = 
testContext().get(TestContextKey.LOAN_CREATE_RESPONSE);
         long loanId = loanResponse.getLoanId();
 
@@ -100,6 +107,10 @@ public class LoanRescheduleStepDef extends AbstractStepDef 
{
 
         ok(() -> 
fineractClient.rescheduleLoans().updateLoanRescheduleRequest(scheduleId, 
approveRequest,
                 Map.<String, Object>of("command", "approve")));
+
+        if (newInterestRate != null) {
+            eventCheckHelper.loanBalanceChangedEventCheck(loanId);
+        }
     }
 
     @Then("Loan reschedule with the following data results a {int} error and 
{string} error message")
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/domain/SmsCampaignRepository.java
 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/domain/SmsCampaignRepository.java
index a36d43c12b..6c06210b83 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/domain/SmsCampaignRepository.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/domain/SmsCampaignRepository.java
@@ -37,4 +37,8 @@ public interface SmsCampaignRepository extends 
JpaRepository<SmsCampaign, Long>,
 
     @Query("SELECT campaign FROM SmsCampaign campaign WHERE 
campaign.paramValue LIKE :reportPattern AND campaign.triggerType=:triggerType 
AND campaign.status=300")
     List<SmsCampaign> findActiveSmsCampaigns(@Param("reportPattern") String 
reportPattern, @Param("triggerType") Integer triggerType);
+
+    boolean existsByCampaignName(String campaignName);
+
+    boolean existsByCampaignNameAndIdNot(String campaignName, Long id);
 }
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/exception/SmsCampaignNameAlreadyExistsException.java
 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/exception/SmsCampaignNameAlreadyExistsException.java
new file mode 100644
index 0000000000..6bc7e40dbc
--- /dev/null
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/exception/SmsCampaignNameAlreadyExistsException.java
@@ -0,0 +1,28 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.infrastructure.campaigns.sms.exception;
+
+import 
org.apache.fineract.infrastructure.core.exception.AbstractPlatformDomainRuleException;
+
+public class SmsCampaignNameAlreadyExistsException extends 
AbstractPlatformDomainRuleException {
+
+    public SmsCampaignNameAlreadyExistsException(final String name) {
+        super("error.msg.sms.campaign.duplicate.name", "An SMS campaign with 
name '" + name + "' already exists", name);
+    }
+}
diff --git 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/service/SmsCampaignWritePlatformServiceJpaImpl.java
 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/service/SmsCampaignWritePlatformServiceJpaImpl.java
index db4b9e82ea..ecbf6d56c9 100644
--- 
a/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/service/SmsCampaignWritePlatformServiceJpaImpl.java
+++ 
b/fineract-provider/src/main/java/org/apache/fineract/infrastructure/campaigns/sms/service/SmsCampaignWritePlatformServiceJpaImpl.java
@@ -47,6 +47,7 @@ import 
org.apache.fineract.infrastructure.campaigns.sms.domain.SmsCampaign;
 import 
org.apache.fineract.infrastructure.campaigns.sms.domain.SmsCampaignRepository;
 import 
org.apache.fineract.infrastructure.campaigns.sms.exception.SmsCampaignMustBeClosedToBeDeletedException;
 import 
org.apache.fineract.infrastructure.campaigns.sms.exception.SmsCampaignMustBeClosedToEditException;
+import 
org.apache.fineract.infrastructure.campaigns.sms.exception.SmsCampaignNameAlreadyExistsException;
 import 
org.apache.fineract.infrastructure.campaigns.sms.exception.SmsCampaignNotFound;
 import 
org.apache.fineract.infrastructure.campaigns.sms.serialization.SmsCampaignValidator;
 import org.apache.fineract.infrastructure.core.api.JsonCommand;
@@ -104,20 +105,32 @@ public class SmsCampaignWritePlatformServiceJpaImpl 
implements SmsCampaignWriteP
     @Transactional
     @Override
     public CommandProcessingResult create(JsonCommand command) {
-        final AppUser currentUser = this.context.authenticatedUser();
-        this.smsCampaignValidator.validateCreate(command.json());
-        final Long runReportId = 
command.longValueOfParameterNamed(SmsCampaignValidator.runReportId);
-        Report report = 
this.reportRepository.findById(runReportId).orElseThrow(() -> new 
ReportNotFoundException(runReportId));
-        LocalDateTime tenantDateTime = DateUtils.getLocalDateTimeOfTenant();
-        SmsCampaign smsCampaign = SmsCampaign.instance(currentUser, report, 
command);
-        LocalDateTime recurrenceStartDate = 
smsCampaign.getRecurrenceStartDate();
-        if (recurrenceStartDate != null && 
DateUtils.isBefore(recurrenceStartDate, tenantDateTime)) {
-            throw new 
GeneralPlatformDomainRuleException("error.msg.campaign.recurrenceStartDate.in.the.past",
-                    "Recurrence start date cannot be the past date.", 
recurrenceStartDate);
-        }
-        this.smsCampaignRepository.saveAndFlush(smsCampaign);
+        try {
+            final AppUser currentUser = this.context.authenticatedUser();
+            this.smsCampaignValidator.validateCreate(command.json());
 
-        return new 
CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(smsCampaign.getId()).build();
+            final String campaignName = 
command.stringValueOfParameterNamed(SmsCampaignValidator.campaignName);
+            if (this.smsCampaignRepository.existsByCampaignName(campaignName)) 
{
+                throw new SmsCampaignNameAlreadyExistsException(campaignName);
+            }
+
+            final Long runReportId = 
command.longValueOfParameterNamed(SmsCampaignValidator.runReportId);
+            Report report = 
this.reportRepository.findById(runReportId).orElseThrow(() -> new 
ReportNotFoundException(runReportId));
+            LocalDateTime tenantDateTime = 
DateUtils.getLocalDateTimeOfTenant();
+            SmsCampaign smsCampaign = SmsCampaign.instance(currentUser, 
report, command);
+            LocalDateTime recurrenceStartDate = 
smsCampaign.getRecurrenceStartDate();
+            if (recurrenceStartDate != null && 
DateUtils.isBefore(recurrenceStartDate, tenantDateTime)) {
+                throw new 
GeneralPlatformDomainRuleException("error.msg.campaign.recurrenceStartDate.in.the.past",
+                        "Recurrence start date cannot be the past date.", 
recurrenceStartDate);
+            }
+            this.smsCampaignRepository.saveAndFlush(smsCampaign);
+
+            return new 
CommandProcessingResultBuilder().withCommandId(command.commandId()).withEntityId(smsCampaign.getId()).build();
+        } catch (final JpaSystemException | DataIntegrityViolationException 
dve) {
+            final Throwable throwable = dve.getMostSpecificCause();
+            handleDataIntegrityIssues(command, throwable);
+            return CommandProcessingResult.empty();
+        }
     }
 
     @Transactional
@@ -135,6 +148,13 @@ public class SmsCampaignWritePlatformServiceJpaImpl 
implements SmsCampaignWriteP
             }
             final Map<String, Object> changes = smsCampaign.update(command);
 
+            if (changes.containsKey(SmsCampaignValidator.campaignName)) {
+                final String newName = (String) 
changes.get(SmsCampaignValidator.campaignName);
+                if 
(this.smsCampaignRepository.existsByCampaignNameAndIdNot(newName, resourceId)) {
+                    throw new SmsCampaignNameAlreadyExistsException(newName);
+                }
+            }
+
             if (changes.containsKey(SmsCampaignValidator.runReportId)) {
                 final Long newValue = 
command.longValueOfParameterNamed(SmsCampaignValidator.runReportId);
                 final Report reportId = 
this.reportRepository.findById(newValue).orElseThrow(() -> new 
ReportNotFoundException(newValue));
@@ -538,6 +558,10 @@ public class SmsCampaignWritePlatformServiceJpaImpl 
implements SmsCampaignWriteP
     }
 
     private void handleDataIntegrityIssues(final JsonCommand command, final 
Throwable realCause) {
+        if (realCause.getMessage().contains("campaign_name_UNIQUE")) {
+            final String name = 
command.stringValueOfParameterNamed(SmsCampaignValidator.campaignName);
+            throw new SmsCampaignNameAlreadyExistsException(name);
+        }
         throw ErrorHandler.getMappable(realCause, 
"error.msg.sms.campaign.unknown.data.integrity.issue",
                 "Unknown data integrity issue with resource: " + 
realCause.getMessage());
     }
diff --git 
a/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml 
b/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml
index e3892d613f..faf3484d67 100644
--- 
a/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml
+++ 
b/fineract-provider/src/main/resources/db/changelog/tenant/changelog-tenant.xml
@@ -234,4 +234,5 @@
     <include file="parts/0213_transaction_summary_adding_originators.xml" 
relativeToChangelogFile="true" />
     <include file="parts/0214_trial_balance_summary_adding_originators.xml" 
relativeToChangelogFile="true" />
     <include 
file="parts/0215_transaction_summary_reports_add_buydown_fee_types.xml" 
relativeToChangelogFile="true" />
+    <include file="parts/0216_add_unique_constraint_sms_campaign_name.xml" 
relativeToChangelogFile="true" />
 </databaseChangeLog>
diff --git 
a/fineract-provider/src/main/resources/db/changelog/tenant/parts/0216_add_unique_constraint_sms_campaign_name.xml
 
b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0216_add_unique_constraint_sms_campaign_name.xml
new file mode 100644
index 0000000000..50f7a57337
--- /dev/null
+++ 
b/fineract-provider/src/main/resources/db/changelog/tenant/parts/0216_add_unique_constraint_sms_campaign_name.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    Licensed to the Apache Software Foundation (ASF) under one
+    or more contributor license agreements. See the NOTICE file
+    distributed with this work for additional information
+    regarding copyright ownership. The ASF licenses this file
+    to you under the Apache License, Version 2.0 (the
+    "License"); you may not use this file except in compliance
+    with the License. You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing,
+    software distributed under the License is distributed on an
+    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+    KIND, either express or implied. See the License for the
+    specific language governing permissions and limitations
+    under the License.
+
+-->
+<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog";
+                   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
+                   
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog 
http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-4.1.xsd";>
+    <changeSet author="fineract" id="1">
+        <addUniqueConstraint columnNames="campaign_name" 
constraintName="campaign_name_UNIQUE"
+                             tableName="sms_campaign"/>
+    </changeSet>
+</databaseChangeLog>
diff --git 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/GroupSavingsIntegrationTest.java
 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/GroupSavingsIntegrationTest.java
index 91a518be69..bbe2d62eed 100644
--- 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/GroupSavingsIntegrationTest.java
+++ 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/GroupSavingsIntegrationTest.java
@@ -1071,7 +1071,7 @@ public class GroupSavingsIntegrationTest {
     }
 
     /**
-     * Test that using a client ID with GROUP guarantor type fails with 
appropriate error
+     * Test that using a non-existent entity ID with GROUP guarantor type 
fails with appropriate error
      */
     @Test
     public void testGroupGuarantorWithClientIdButGroupType() {
@@ -1110,9 +1110,11 @@ public class GroupSavingsIntegrationTest {
                     .build(loanClientID.toString(), loanProductID.toString(), 
null);
             final Integer loanID = 
this.loanTransactionHelper.getLoanId(loanApplicationJSON);
 
-            // Try to create guarantor with CLIENT ID but GROUP type (type 
mismatch)
+            // Try to create guarantor with a non-existent GROUP entity ID 
(type mismatch)
+            // Use a random large ID that cannot collide with any 
auto-generated group ID from other tests
+            final Integer nonExistentGroupId = Utils.randomNumberGenerator(7);
             String guarantorJSON = new GuarantorTestBuilder()
-                    
.existingGroupWithGuaranteeAmount(String.valueOf(otherClientID), 
String.valueOf(clientSavingsId), GUARANTEE_AMOUNT)
+                    
.existingGroupWithGuaranteeAmount(String.valueOf(nonExistentGroupId), 
String.valueOf(clientSavingsId), GUARANTEE_AMOUNT)
                     .build();
 
             final ResponseSpecification errorResponse = new 
ResponseSpecBuilder().build();
@@ -1121,10 +1123,9 @@ public class GroupSavingsIntegrationTest {
 
             ArrayList<HashMap> error = (ArrayList<HashMap>) 
this.guarantorHelper.createGuarantorWithError(loanID, guarantorJSON,
                     errorRequest, errorResponse);
-            // Verify we got an error response (status code may be 403 or 404 
depending on environment)
-            Assertions.assertNotNull(error, "Should return error for client ID 
used with GROUP type");
+            Assertions.assertNotNull(error, "Should return error for 
non-existent group entity ID");
 
-            LOG.info("SUCCESS: Client ID with GROUP type correctly rejected");
+            LOG.info("SUCCESS: Non-existent group entity ID correctly 
rejected");
         });
     }
 
diff --git 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/SmsCampaignIntegrationTest.java
 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/SmsCampaignIntegrationTest.java
new file mode 100644
index 0000000000..04193ff99b
--- /dev/null
+++ 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/SmsCampaignIntegrationTest.java
@@ -0,0 +1,115 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.fineract.integrationtests;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+import io.restassured.builder.RequestSpecBuilder;
+import io.restassured.builder.ResponseSpecBuilder;
+import io.restassured.http.ContentType;
+import io.restassured.specification.RequestSpecification;
+import io.restassured.specification.ResponseSpecification;
+import java.util.HashMap;
+import java.util.List;
+import org.apache.fineract.integrationtests.common.CommonConstants;
+import org.apache.fineract.integrationtests.common.Utils;
+import 
org.apache.fineract.integrationtests.common.organisation.CampaignsHelper;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockserver.integration.ClientAndServer;
+import org.mockserver.junit.jupiter.MockServerExtension;
+import org.mockserver.junit.jupiter.MockServerSettings;
+import org.mockserver.model.HttpRequest;
+import org.mockserver.model.HttpResponse;
+import org.mockserver.model.MediaType;
+
+/**
+ * Integration tests for SMS Campaign duplicate name validation.
+ */
+@ExtendWith(MockServerExtension.class)
+@MockServerSettings(ports = { 9191 })
+public class SmsCampaignIntegrationTest {
+
+    private RequestSpecification requestSpec;
+    private ResponseSpecification responseSpec;
+    private ResponseSpecification errorResponseSpec;
+    private CampaignsHelper campaignsHelper;
+    private final ClientAndServer client;
+
+    public SmsCampaignIntegrationTest(ClientAndServer client) {
+        this.client = client;
+        
this.client.when(HttpRequest.request().withMethod("GET").withPath("/smsbridges"))
+                
.respond(HttpResponse.response().withContentType(MediaType.APPLICATION_JSON).withBody(
+                        
"[{\"id\":1,\"tenantId\":1,\"phoneNo\":\"+1234567890\",\"providerName\":\"Dummy 
SMS Provider - Testing\",\"providerDescription\":\"Dummy, just for 
testing\"}]"));
+    }
+
+    @BeforeEach
+    public void setup() {
+        Utils.initializeRESTAssured();
+        this.requestSpec = new 
RequestSpecBuilder().setContentType(ContentType.JSON).build();
+        this.requestSpec.header("Authorization", "Basic " + 
Utils.loginIntoServerAndGetBase64EncodedAuthenticationKey());
+        this.requestSpec.header("Fineract-Platform-TenantId", "default");
+        this.responseSpec = new 
ResponseSpecBuilder().expectStatusCode(200).build();
+        this.errorResponseSpec = new 
ResponseSpecBuilder().expectStatusCode(403).build();
+        this.campaignsHelper = new CampaignsHelper(this.requestSpec, 
this.responseSpec);
+    }
+
+    @Test
+    public void testCreateCampaignWithDuplicateNameShouldFail() {
+        String reportName = "Prospective Clients";
+        int triggerType = 1;
+        String campaignName = "Duplicate_Test_Campaign_" + 
System.currentTimeMillis();
+
+        // Create first campaign with specific name
+        Integer firstCampaignId = 
campaignsHelper.createCampaignWithName(reportName, triggerType, campaignName);
+        assertNotNull(firstCampaignId, "First campaign should be created 
successfully");
+        campaignsHelper.verifyCampaignCreatedOnServer(requestSpec, 
responseSpec, firstCampaignId);
+
+        // Attempt to create second campaign with the same name - should fail
+        List<HashMap> errors = 
campaignsHelper.createCampaignWithNameExpectingError(errorResponseSpec, 
reportName, triggerType,
+                campaignName);
+
+        assertNotNull(errors, "Error response should not be null");
+        assertEquals(1, errors.size(), "Should have exactly one error");
+        assertEquals("error.msg.sms.campaign.duplicate.name", 
errors.get(0).get(CommonConstants.RESPONSE_ERROR_MESSAGE_CODE),
+                "Error code should indicate duplicate campaign name");
+    }
+
+    @Test
+    public void testCreateCampaignWithUniqueNameShouldSucceed() {
+        String reportName = "Prospective Clients";
+        int triggerType = 1;
+        String campaignName1 = "Unique_Campaign_1_" + 
System.currentTimeMillis();
+        String campaignName2 = "Unique_Campaign_2_" + 
System.currentTimeMillis();
+
+        // Create first campaign
+        Integer firstCampaignId = 
campaignsHelper.createCampaignWithName(reportName, triggerType, campaignName1);
+        assertNotNull(firstCampaignId, "First campaign should be created 
successfully");
+
+        // Create second campaign with different name - should succeed
+        Integer secondCampaignId = 
campaignsHelper.createCampaignWithName(reportName, triggerType, campaignName2);
+        assertNotNull(secondCampaignId, "Second campaign with different name 
should be created successfully");
+
+        // Verify both campaigns exist
+        campaignsHelper.verifyCampaignCreatedOnServer(requestSpec, 
responseSpec, firstCampaignId);
+        campaignsHelper.verifyCampaignCreatedOnServer(requestSpec, 
responseSpec, secondCampaignId);
+    }
+}
diff --git 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/organisation/CampaignsHelper.java
 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/organisation/CampaignsHelper.java
index 6e16ae923d..e0c40c6fad 100644
--- 
a/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/organisation/CampaignsHelper.java
+++ 
b/integration-tests/src/test/java/org/apache/fineract/integrationtests/common/organisation/CampaignsHelper.java
@@ -65,6 +65,21 @@ public class CampaignsHelper {
                 "resourceId");
     }
 
+    public Integer createCampaignWithName(String reportName, Integer 
triggerType, String campaignName) {
+        log.info("---------------------------------CREATING A CAMPAIGN WITH 
NAME---------------------------------------------");
+        final String CREATE_SMS_CAMPAIGNS_URL = SMS_CAMPAIGNS_URL + "?" + 
Utils.TENANT_IDENTIFIER;
+        return Utils.performServerPost(requestSpec, responseSpec, 
CREATE_SMS_CAMPAIGNS_URL,
+                getCreateCampaignJSONWithName(reportName, triggerType, 
campaignName), "resourceId");
+    }
+
+    public List<HashMap> 
createCampaignWithNameExpectingError(ResponseSpecification errorResponseSpec, 
String reportName,
+            Integer triggerType, String campaignName) {
+        log.info("---------------------------------CREATING A CAMPAIGN WITH 
NAME (EXPECTING ERROR)---------------------");
+        final String CREATE_SMS_CAMPAIGNS_URL = SMS_CAMPAIGNS_URL + "?" + 
Utils.TENANT_IDENTIFIER;
+        return Utils.performServerPost(requestSpec, errorResponseSpec, 
CREATE_SMS_CAMPAIGNS_URL,
+                getCreateCampaignJSONWithName(reportName, triggerType, 
campaignName), "errors");
+    }
+
     // TODO: Rewrite to use fineract-client instead!
     // Example: 
org.apache.fineract.integrationtests.common.loans.LoanTransactionHelper.disburseLoan(java.lang.Long,
     // org.apache.fineract.client.models.PostLoansLoanIdRequest)
@@ -132,6 +147,10 @@ public class CampaignsHelper {
     // org.apache.fineract.client.models.PostLoansLoanIdRequest)
     @Deprecated(forRemoval = true)
     public String getCreateCampaignJSON(String reportName, Integer 
triggerType) {
+        return getCreateCampaignJSONWithName(reportName, triggerType, 
Utils.randomStringGenerator("Campaign_Name_", 5));
+    }
+
+    public String getCreateCampaignJSONWithName(String reportName, Integer 
triggerType, String campaignName) {
         final HashMap<String, Object> map = new HashMap<>();
         final HashMap<String, Object> paramValueMap = new HashMap<>();
         Long reportId = getSelectedReportId(reportName);
@@ -143,7 +162,7 @@ public class CampaignsHelper {
             map.put("frequency", 1);
             map.put("interval", "1");
         }
-        map.put("campaignName", Utils.randomStringGenerator("Campaign_Name_", 
5));
+        map.put("campaignName", campaignName);
         map.put("campaignType", 1);
         map.put("message", "Hi, this is from integtration tests runner");
         map.put("locale", "en");

Reply via email to