Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,13 @@ All notable changes to the **Sinch Java SDK** are documented in this file.

---

## v2.1 – unreleased

### Conversation
- **[feature]** Support `Consents` API: `listIdentities` and `listAuditRecords` endpoints

---

## v2.0 – 2026-03-31

### Major breaking changes with major release
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,4 +98,12 @@ public interface ConversationService {
* @since 2.0
*/
TemplatesService templates();

/**
* Consents Service instance
*
* @return service instance for project
* @since 2.1
*/
ConsentsService consents();
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import com.sinch.sdk.core.utils.StringUtil;
import com.sinch.sdk.domains.conversation.api.v1.AppsService;
import com.sinch.sdk.domains.conversation.api.v1.CapabilityService;
import com.sinch.sdk.domains.conversation.api.v1.ConsentsService;
import com.sinch.sdk.domains.conversation.api.v1.ContactsService;
import com.sinch.sdk.domains.conversation.api.v1.ConversationsService;
import com.sinch.sdk.domains.conversation.api.v1.EventDestinationsService;
Expand Down Expand Up @@ -66,6 +67,7 @@ public class ConversationService

private volatile Map<String, AuthManager> authManagers;
private volatile AppsService apps;
private volatile ConsentsService consents;
private volatile ContactsService contacts;
private volatile MessagesService messages;
private volatile ConversationsService conversations;
Expand Down Expand Up @@ -112,6 +114,24 @@ public AppsService apps() {
return this.apps;
}

public ConsentsService consents() {
if (null == this.consents) {
synchronized (this) {
if (null == this.consents) {
instanceLazyInit();
this.consents =
new ConsentsServiceImpl(
httpClientSupplier.get(),
context.getServer(),
authManagers,
HttpMapper.getInstance(),
uriUUID);
}
}
}
return this.consents;
}

public ContactsService contacts() {
if (null == this.contacts) {
synchronized (this) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
package com.sinch.sdk.domains.conversation.api.v1.adapters;

import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.when;

import com.adelean.inject.resources.junit.jupiter.GivenTextResource;
import com.adelean.inject.resources.junit.jupiter.TestWithResources;
import com.sinch.sdk.BaseTest;
import com.sinch.sdk.core.TestHelpers;
import com.sinch.sdk.core.exceptions.ApiException;
import com.sinch.sdk.core.http.AuthManager;
import com.sinch.sdk.core.http.HttpClient;
import com.sinch.sdk.core.http.HttpContentType;
import com.sinch.sdk.core.http.HttpMapper;
import com.sinch.sdk.core.http.HttpMethod;
import com.sinch.sdk.core.http.HttpRequest;
import com.sinch.sdk.core.http.HttpRequestTest.HttpRequestMatcher;
import com.sinch.sdk.core.http.HttpResponse;
import com.sinch.sdk.core.http.URLParameter;
import com.sinch.sdk.core.http.URLParameter.STYLE;
import com.sinch.sdk.core.http.URLPathUtils;
import com.sinch.sdk.core.models.ServerConfiguration;
import com.sinch.sdk.domains.conversation.api.v1.ConsentsService;
import com.sinch.sdk.domains.conversation.models.v1.consents.ConsentsDtoTest;
import com.sinch.sdk.domains.conversation.models.v1.consents.ConsentsListType;
import com.sinch.sdk.domains.conversation.models.v1.consents.Identity;
import com.sinch.sdk.domains.conversation.models.v1.consents.request.ConsentsListQueryParameters;
import com.sinch.sdk.domains.conversation.models.v1.consents.response.AuditRecordsResponse;
import com.sinch.sdk.domains.conversation.models.v1.consents.response.ConsentsListResponse;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.Map;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mock;

@TestWithResources
class ConsentsServiceTest extends BaseTest {

@Mock HttpClient httpClient;
@Mock ServerConfiguration serverConfiguration;
@Mock Map<String, AuthManager> authManagers;

static final String uriUUID = "foo";
static final String APP_ID = "an app id";
static final String IDENTITY = "an identity value";
static final Collection<String> AUTH_NAMES = Arrays.asList("Basic", "oAuth2");

ConsentsService service;

@GivenTextResource("/domains/conversation/v1/consents/ConsentsListResponseDtoPage0.json")
String jsonConsentsListResponseDtoPage0;

@GivenTextResource("/domains/conversation/v1/consents/ConsentsListResponseDtoPage1.json")
String jsonConsentsListResponseDtoPage1;

@GivenTextResource("/domains/conversation/v1/consents/AuditRecordsResponseDto.json")
String jsonAuditRecordsResponseDto;

@BeforeEach
public void initMocks() {
service =
new ConsentsServiceImpl(
httpClient, serverConfiguration, authManagers, HttpMapper.getInstance(), uriUUID);
}

@Test
void listIdentities() throws ApiException {

HttpRequest httpRequest1 =
new HttpRequest(
String.format(
"/v1/projects/%s/apps/%s/consents/%s",
URLPathUtils.encodePathSegment(uriUUID),
URLPathUtils.encodePathSegment(APP_ID),
URLPathUtils.encodePathSegment(ConsentsListType.OPT_OUT_ALL.toString())),
HttpMethod.GET,
Collections.emptyList(),
(String) null,
Collections.emptyMap(),
Collections.singletonList(HttpContentType.APPLICATION_JSON),
Collections.emptyList(),
AUTH_NAMES);
HttpRequest httpRequest2 =
new HttpRequest(
String.format(
"/v1/projects/%s/apps/%s/consents/%s",
URLPathUtils.encodePathSegment(uriUUID),
URLPathUtils.encodePathSegment(APP_ID),
URLPathUtils.encodePathSegment(ConsentsListType.OPT_OUT_ALL.toString())),
HttpMethod.GET,
Collections.singletonList(
new URLParameter("page_token", "the next page token value", STYLE.FORM, true)),
(String) null,
Collections.emptyMap(),
Collections.singletonList(HttpContentType.APPLICATION_JSON),
Collections.emptyList(),
AUTH_NAMES);
HttpResponse httpResponse1 =
new HttpResponse(
200, null, Collections.emptyMap(), jsonConsentsListResponseDtoPage0.getBytes());
HttpResponse httpResponse2 =
new HttpResponse(
200, null, Collections.emptyMap(), jsonConsentsListResponseDtoPage1.getBytes());

when(httpClient.invokeAPI(
eq(serverConfiguration),
eq(authManagers),
argThat(new HttpRequestMatcher(httpRequest1))))
.thenReturn(httpResponse1);
when(httpClient.invokeAPI(
eq(serverConfiguration),
eq(authManagers),
argThat(new HttpRequestMatcher(httpRequest2))))
.thenReturn(httpResponse2);

ConsentsListResponse response = service.listIdentities(APP_ID, ConsentsListType.OPT_OUT_ALL);

Iterator<Identity> iterator = response.iterator();

Assertions.assertThat(iterator.hasNext()).isEqualTo(true);
Identity item = iterator.next();
TestHelpers.recursiveEquals(item, ConsentsDtoTest.expectedIdentityDto);

item = iterator.next();
TestHelpers.recursiveEquals(
item, Identity.builder().setIdentity("a 2nd identity value").build());
Assertions.assertThat(iterator.hasNext()).isEqualTo(true);

item = iterator.next();
TestHelpers.recursiveEquals(
item, Identity.builder().setIdentity("a 3rd identity value").build());
Assertions.assertThat(iterator.hasNext()).isEqualTo(false);
}

@Test
void listIdentitiesWithQueryParameters() throws ApiException {

HttpRequest httpRequest =
new HttpRequest(
String.format(
"/v1/projects/%s/apps/%s/consents/%s",
URLPathUtils.encodePathSegment(uriUUID),
URLPathUtils.encodePathSegment(APP_ID),
URLPathUtils.encodePathSegment(ConsentsListType.OPT_OUT_MARKETING.toString())),
HttpMethod.GET,
Collections.singletonList(new URLParameter("page_size", 5, STYLE.FORM, true)),
(String) null,
Collections.emptyMap(),
Collections.singletonList(HttpContentType.APPLICATION_JSON),
Collections.emptyList(),
AUTH_NAMES);
HttpResponse httpResponse =
new HttpResponse(
200, null, Collections.emptyMap(), jsonConsentsListResponseDtoPage1.getBytes());

when(httpClient.invokeAPI(
eq(serverConfiguration),
eq(authManagers),
argThat(new HttpRequestMatcher(httpRequest))))
.thenReturn(httpResponse);

ConsentsListQueryParameters queryParams =
ConsentsListQueryParameters.builder().setPageSize(5).build();
ConsentsListResponse response =
service.listIdentities(APP_ID, ConsentsListType.OPT_OUT_MARKETING, queryParams);

Assertions.assertThat(response.getContent()).isNotEmpty();
}

@Test
void listAuditRecords() throws ApiException {

HttpRequest httpRequest =
new HttpRequest(
String.format(
"/v1/projects/%s/apps/%s/consents/identities/%s",
URLPathUtils.encodePathSegment(uriUUID),
URLPathUtils.encodePathSegment(APP_ID),
URLPathUtils.encodePathSegment(IDENTITY)),
HttpMethod.GET,
Collections.emptyList(),
(String) null,
Collections.emptyMap(),
Collections.singletonList(HttpContentType.APPLICATION_JSON),
Collections.emptyList(),
AUTH_NAMES);
HttpResponse httpResponse =
new HttpResponse(200, null, Collections.emptyMap(), jsonAuditRecordsResponseDto.getBytes());

when(httpClient.invokeAPI(
eq(serverConfiguration),
eq(authManagers),
argThat(new HttpRequestMatcher(httpRequest))))
.thenReturn(httpResponse);

AuditRecordsResponse response = service.listAuditRecords(APP_ID, IDENTITY);

TestHelpers.recursiveEquals(response, ConsentsDtoTest.expectedAuditRecordsResponseDto);
}
}
Original file line number Diff line number Diff line change
@@ -1,57 +1,101 @@
package com.sinch.sdk.e2e.domains.conversation;

import com.sinch.sdk.core.TestHelpers;
import com.sinch.sdk.domains.conversation.api.v1.ConsentsService;
import com.sinch.sdk.domains.conversation.models.v1.consents.ConsentsListType;
import com.sinch.sdk.domains.conversation.models.v1.consents.request.ConsentsListQueryParameters;
import com.sinch.sdk.domains.conversation.models.v1.consents.response.AuditRecord;
import com.sinch.sdk.domains.conversation.models.v1.consents.response.AuditRecordsResponse;
import com.sinch.sdk.domains.conversation.models.v1.consents.response.ConsentsListResponse;
import com.sinch.sdk.e2e.Config;
import io.cucumber.java.en.Given;
import io.cucumber.java.en.Then;
import io.cucumber.java.en.When;
import java.time.Instant;
import java.util.Iterator;
import org.junit.jupiter.api.Assertions;

public class ConsentsSteps {

static final String APP_ID = AppsSteps.APP_ID;
static final String IDENTITY = "33612345678";

ConsentsService service;
ConsentsListResponse listPageResponse;
AuditRecordsResponse listAuditRecordsResponse;

@Given("^the Conversation service \"Consents\" is available$")
public void serviceAvailable() {
// TODO implement conversation consents steps
service = Config.getSinchClient().conversation().v1().consents();
}

@When("^I send a request to list the existing Consent Identities$")
public void listPage() {

// TODO implement conversation consents steps
ConsentsListQueryParameters queryParams =
ConsentsListQueryParameters.builder().setPageSize(10).build();
listPageResponse = service.listIdentities(APP_ID, ConsentsListType.OPT_OUT_ALL, queryParams);
}

@When("^I send a request to list all the Consent Identities$")
public void listAll() {

// TODO implement conversation consents steps
ConsentsListQueryParameters queryParams =
ConsentsListQueryParameters.builder().setPageSize(10).build();
listPageResponse = service.listIdentities(APP_ID, ConsentsListType.OPT_OUT_ALL, queryParams);
}

@When("^I iterate manually over the Consent Identities pages$")
public void listIterateManually() {

// TODO implement conversation consents steps
ConsentsListQueryParameters queryParams =
ConsentsListQueryParameters.builder().setPageSize(10).build();
listPageResponse = service.listIdentities(APP_ID, ConsentsListType.OPT_OUT_ALL, queryParams);
}

@When("^I send a request to list the Audit Records associated with an identity$")
public void listAuditRecords() {

// TODO implement conversation consents steps
listAuditRecordsResponse = service.listAuditRecords(APP_ID, IDENTITY);
}

@Then("the response contains \"{int}\" Consent Identities")
public void listPageResult(int count) {
// TODO implement conversation consents steps
Assertions.assertEquals(count, listPageResponse.getContent().size());
}

@Then("the Consent Identities list contains \"{int}\" Consent Identities")
public void listAllResult(int count) {
// TODO implement conversation consents steps
Iterator<?> iterator = listPageResponse.iterator();
TestHelpers.checkIteratorItems(iterator, count);
}

@Then("the Consent Identities iteration result contains the data from \"{int}\" pages")
public void listPageIterateResult(int count) {
// TODO implement conversation consents steps
int pageCount = 0;
ConsentsListResponse currentPage = listPageResponse;
do {
pageCount++;
if (!currentPage.hasNextPage()) {
break;
}
currentPage = currentPage.nextPage();
} while (true);

Assertions.assertEquals(pageCount, count);
}

@Then("the response contains list of the Audit Records associated with an identity")
public void listAuditRecordsResult() {
// TODO implement conversation consents steps
Assertions.assertNotNull(listAuditRecordsResponse);
Assertions.assertNotNull(listAuditRecordsResponse.getIdentity());
Assertions.assertEquals("33612345678", listAuditRecordsResponse.getIdentity().getIdentity());

Assertions.assertNotNull(listAuditRecordsResponse.getAuditRecords());
Assertions.assertEquals(1, listAuditRecordsResponse.getAuditRecords().size());

AuditRecord record = listAuditRecordsResponse.getAuditRecords().get(0);
Assertions.assertEquals(AuditRecord.OriginEnum.ORIGIN_MO, record.getOrigin());
Assertions.assertEquals(AuditRecord.OperationEnum.OPERATION_INSERT, record.getOperation());
Assertions.assertEquals(ConsentsListType.OPT_OUT_ALL, record.getListType());
Assertions.assertEquals("123coffee-dada-beef-cafe-baadc0de5678", record.getProjectId());
Assertions.assertEquals(APP_ID, record.getAppId());
Assertions.assertEquals(Instant.parse("2025-06-06T14:42:56.031323Z"), record.getDatetime());
}
}
3 changes: 3 additions & 0 deletions examples/snippets/src/main/java/conversation/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ See main [README.md](../../../../README.md) for how to execute snippets
- [conversation/applications/Update](./applications/Update.java)
- Capability
- [conversation/capability/Capability](./capability/Capability.java)
- Consents
- [conversation/consents/ListIdentities](./consents/ListIdentities.java)
- [conversation/consents/ListAuditRecords](./consents/ListAuditRecords.java)
- Contacts
- [conversation/contacts/List](./contacts/List.java)
- [conversation/contacts/Create](./contacts/Create.java)
Expand Down
Loading
Loading