From 32f7c2abf744caeddd45e3eed843f145176ead45 Mon Sep 17 00:00:00 2001 From: dashevchenko Date: Tue, 13 Jan 2026 17:44:31 +0200 Subject: [PATCH] fixed smsEnabled property handling for tenant profile configuration --- .../DefaultTbApiUsageStateServiceTest.java | 84 +++++++++++++++++++ .../usagerecord/ApiUsageStateServiceImpl.java | 28 +++++-- 2 files changed, 103 insertions(+), 9 deletions(-) diff --git a/application/src/test/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateServiceTest.java b/application/src/test/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateServiceTest.java index 7530c646f4..a358c0f12c 100644 --- a/application/src/test/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateServiceTest.java +++ b/application/src/test/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateServiceTest.java @@ -27,6 +27,7 @@ import org.thingsboard.server.common.data.TenantProfile; import org.thingsboard.server.common.data.id.ApiUsageStateId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.kv.TsKvEntry; import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration; import org.thingsboard.server.common.data.tenant.profile.TenantProfileData; import org.thingsboard.server.common.msg.queue.TbCallback; @@ -40,6 +41,7 @@ import java.lang.reflect.Field; import java.time.LocalDate; import java.util.HashMap; import java.util.Map; +import java.util.Optional; import java.util.UUID; import java.util.concurrent.TimeUnit; @@ -47,7 +49,10 @@ import static java.time.ZoneOffset.UTC; import static java.time.temporal.ChronoField.DAY_OF_MONTH; import static java.time.temporal.ChronoUnit.MONTHS; import static org.assertj.core.api.Assertions.assertThat; +import static org.awaitility.Awaitility.await; import static org.junit.Assert.assertEquals; +import static org.thingsboard.server.common.data.ApiFeature.SMS; +import static org.thingsboard.server.common.data.ApiUsageRecordKey.SMS_EXEC_COUNT; @DaoSqlTest public class DefaultTbApiUsageStateServiceTest extends AbstractControllerTest { @@ -157,6 +162,85 @@ public class DefaultTbApiUsageStateServiceTest extends AbstractControllerTest { assertThat(tenantApiUsageState.getNextCycleTs()).isEqualTo(firstOfNextMonth); } + @Test + public void checkTenantCreatedWithSmsDisabledApiUsage() throws Exception { + TenantProfile tenantProfile = new TenantProfile(); + tenantProfile.setName("Test profile"); + TenantProfileData tenantProfileData = new TenantProfileData(); + DefaultTenantProfileConfiguration config = DefaultTenantProfileConfiguration.builder() + .smsEnabled(false) + .build(); + + tenantProfileData.setConfiguration(config); + tenantProfile.setProfileData(tenantProfileData); + tenantProfile = doPost("/api/tenantProfile", tenantProfile, TenantProfile.class); + + Tenant tenant = new Tenant(); + tenant.setTitle("My test tenant"); + tenant.setTenantProfileId(tenantProfile.getId()); + tenant = saveTenant(tenant); + + ApiUsageState apiUsageState = apiUsageStateService.findApiUsageStateByEntityId(tenant.getId()); + assertThat(apiUsageState.getSmsExecState()).isEqualTo(ApiUsageStateValue.DISABLED); + + TenantId finalTenantId = tenant.getId(); + ApiUsageStateId finalApiUsageStateId = apiUsageState.getId(); + + + await().atMost(5, TimeUnit.SECONDS).until(() -> { + Optional smsApiState = tsService.findLatest(finalTenantId, finalApiUsageStateId, SMS.getApiStateKey()).get(); + return smsApiState.isPresent() && smsApiState.get().getValueAsString().equals(ApiUsageStateValue.DISABLED.name()); + }); + + await().atMost(5, TimeUnit.SECONDS).until(() -> { + Optional smsApiState = tsService.findLatest(finalTenantId, finalApiUsageStateId, SMS_EXEC_COUNT.getApiLimitKey()).get(); + return smsApiState.isPresent() && smsApiState.get().getLongValue().get().equals(0L); + }); + + // enable SMS and check that the ApiUsageState is updated accordingly + config = DefaultTenantProfileConfiguration.builder() + .smsEnabled(true) + .maxSms(10) + .build(); + tenantProfileData.setConfiguration(config); + tenantProfile.setProfileData(tenantProfileData); + doPost("/api/tenantProfile", tenantProfile, TenantProfile.class); + + await().atMost(5, TimeUnit.SECONDS).until(() -> + apiUsageStateService.findApiUsageStateByEntityId(finalTenantId).getSmsExecState() == ApiUsageStateValue.ENABLED); + + await().atMost(5, TimeUnit.SECONDS).until(() -> { + Optional smsApiState = tsService.findLatest(finalTenantId, finalApiUsageStateId, SMS.getApiStateKey()).get(); + return smsApiState.isPresent() && smsApiState.get().getValueAsString().equals(ApiUsageStateValue.ENABLED.name()); + }); + + await().atMost(5, TimeUnit.SECONDS).until(() -> { + Optional smsApiState = tsService.findLatest(finalTenantId, finalApiUsageStateId, SMS_EXEC_COUNT.getApiLimitKey()).get(); + return smsApiState.isPresent() && smsApiState.get().getLongValue().get().equals(10L); + }); + + //disable SMS and check that the ApiUsageState is updated accordingly + config = DefaultTenantProfileConfiguration.builder() + .smsEnabled(false) + .build(); + tenantProfileData.setConfiguration(config); + tenantProfile.setProfileData(tenantProfileData); + doPost("/api/tenantProfile", tenantProfile, TenantProfile.class); + + await().atMost(5, TimeUnit.SECONDS).until(() -> + apiUsageStateService.findApiUsageStateByEntityId(finalTenantId).getSmsExecState() == ApiUsageStateValue.DISABLED); + + await().atMost(5, TimeUnit.SECONDS).until(() -> { + Optional smsApiState = tsService.findLatest(finalTenantId, finalApiUsageStateId, SMS.getApiStateKey()).get(); + return smsApiState.isPresent() && smsApiState.get().getValueAsString().equals(ApiUsageStateValue.DISABLED.name()); + }); + + await().atMost(5, TimeUnit.SECONDS).until(() -> { + Optional smsApiState = tsService.findLatest(finalTenantId, finalApiUsageStateId, SMS_EXEC_COUNT.getApiLimitKey()).get(); + return smsApiState.isPresent() && smsApiState.get().getLongValue().get().equals(0L); + }); + } + private TenantProfile createTenantProfile() { TenantProfile tenantProfile = new TenantProfile(); tenantProfile.setName("Tenant Profile"); diff --git a/dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiUsageStateServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiUsageStateServiceImpl.java index b97c81c612..7f0e507f5f 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiUsageStateServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiUsageStateServiceImpl.java @@ -35,7 +35,7 @@ import org.thingsboard.server.common.data.kv.BasicTsKvEntry; import org.thingsboard.server.common.data.kv.LongDataEntry; import org.thingsboard.server.common.data.kv.StringDataEntry; import org.thingsboard.server.common.data.kv.TsKvEntry; -import org.thingsboard.server.common.data.tenant.profile.TenantProfileConfiguration; +import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration; import org.thingsboard.server.dao.entity.AbstractEntityService; import org.thingsboard.server.dao.eventsourcing.DeleteEntityEvent; import org.thingsboard.server.dao.eventsourcing.SaveEntityEvent; @@ -101,6 +101,21 @@ public class ApiUsageStateServiceImpl extends AbstractEntityService implements A entityId = Objects.requireNonNullElse(entityId, tenantId); log.trace("Executing createDefaultUsageRecord [{}]", entityId); validateId(tenantId, id -> INCORRECT_TENANT_ID + id); + + ApiUsageStateValue smsApiUsageState = ApiUsageStateValue.ENABLED; + + DefaultTenantProfileConfiguration configuration = null; + if (entityId.getEntityType() == EntityType.TENANT && !entityId.equals(TenantId.SYS_TENANT_ID)) { + tenantId = (TenantId) entityId; + Tenant tenant = tenantService.findTenantById(tenantId); + TenantProfile tenantProfile = tenantProfileDao.findById(tenantId, tenant.getTenantProfileId().getId()); + configuration = (DefaultTenantProfileConfiguration) tenantProfile.getProfileData().getConfiguration(); + + if (configuration.getSmsEnabled() != null && !configuration.getSmsEnabled()) { + smsApiUsageState = ApiUsageStateValue.DISABLED; + } + } + ApiUsageState apiUsageState = new ApiUsageState(); apiUsageState.setTenantId(tenantId); apiUsageState.setEntityId(entityId); @@ -109,7 +124,7 @@ public class ApiUsageStateServiceImpl extends AbstractEntityService implements A apiUsageState.setJsExecState(ApiUsageStateValue.ENABLED); apiUsageState.setTbelExecState(ApiUsageStateValue.ENABLED); apiUsageState.setDbStorageState(ApiUsageStateValue.ENABLED); - apiUsageState.setSmsExecState(ApiUsageStateValue.ENABLED); + apiUsageState.setSmsExecState(smsApiUsageState); apiUsageState.setEmailExecState(ApiUsageStateValue.ENABLED); apiUsageState.setAlarmExecState(ApiUsageStateValue.ENABLED); apiUsageStateValidator.validate(apiUsageState, ApiUsageState::getTenantId); @@ -138,17 +153,12 @@ public class ApiUsageStateServiceImpl extends AbstractEntityService implements A apiUsageStates.add(new BasicTsKvEntry(saved.getCreatedTime(), new StringDataEntry(ApiFeature.EMAIL.getApiStateKey(), ApiUsageStateValue.ENABLED.name()))); apiUsageStates.add(new BasicTsKvEntry(saved.getCreatedTime(), - new StringDataEntry(ApiFeature.SMS.getApiStateKey(), ApiUsageStateValue.ENABLED.name()))); + new StringDataEntry(ApiFeature.SMS.getApiStateKey(), smsApiUsageState.name()))); apiUsageStates.add(new BasicTsKvEntry(saved.getCreatedTime(), new StringDataEntry(ApiFeature.ALARM.getApiStateKey(), ApiUsageStateValue.ENABLED.name()))); tsService.save(tenantId, saved.getId(), apiUsageStates, 0L); - if (entityId.getEntityType() == EntityType.TENANT && !entityId.equals(TenantId.SYS_TENANT_ID)) { - tenantId = (TenantId) entityId; - Tenant tenant = tenantService.findTenantById(tenantId); - TenantProfile tenantProfile = tenantProfileDao.findById(tenantId, tenant.getTenantProfileId().getId()); - TenantProfileConfiguration configuration = tenantProfile.getProfileData().getConfiguration(); - + if (configuration != null) { List profileThresholds = new ArrayList<>(); for (ApiUsageRecordKey key : ApiUsageRecordKey.values()) { if (key.getApiLimitKey() == null) continue;