diff --git a/.gitignore b/.gitignore index ef86395..0e743b2 100644 --- a/.gitignore +++ b/.gitignore @@ -34,3 +34,4 @@ dependency-reduced-pom.xml mem*.db README.html +/CLAUDE.md diff --git a/client-lib/pom.xml b/client-lib/pom.xml index 0550326..9fdb659 100644 --- a/client-lib/pom.xml +++ b/client-lib/pom.xml @@ -5,7 +5,7 @@ com.signnow api-client-lib - 1.0.0-SNAPSHOT + 1.0.8 jar @@ -94,4 +94,13 @@ + + + + github + Darwin + https://maven.pkg.github.com/SSDevelopment/DarwinOperations + + + diff --git a/client-lib/src/main/java/com/signnow/library/SNClient.java b/client-lib/src/main/java/com/signnow/library/SNClient.java index ebbc099..5b9ad4c 100644 --- a/client-lib/src/main/java/com/signnow/library/SNClient.java +++ b/client-lib/src/main/java/com/signnow/library/SNClient.java @@ -5,13 +5,11 @@ import com.signnow.library.dto.User; import com.signnow.library.exceptions.SNApiException; import com.signnow.library.exceptions.SNException; -import com.signnow.library.facades.DocumentGroups; -import com.signnow.library.facades.Documents; -import com.signnow.library.facades.ServiceProvider; -import com.signnow.library.facades.Templates; +import com.signnow.library.facades.*; import com.signnow.library.services.DocumentGroupsService; import com.signnow.library.services.DocumentsService; import com.signnow.library.services.TemplatesService; +import com.signnow.library.services.WebhookService; import javax.ws.rs.client.Entity; import javax.ws.rs.client.Invocation; @@ -36,6 +34,7 @@ public class SNClient implements ServiceProvider { private WebTarget apiWebTarget; private Documents documentsService = new DocumentsService(this); private Templates templatesService = new TemplatesService(this); + private Webhooks webhooksService = new WebhookService(this); private DocumentGroups documentGroupsService = new DocumentGroupsService(this); protected SNClient(WebTarget apiWebTarget, User user) { @@ -61,6 +60,11 @@ public Templates templatesService() { return templatesService; } + @Override + public Webhooks webhooksService() { + return webhooksService; + } + @Override public DocumentGroups documentGroupsService() { return documentGroupsService; diff --git a/client-lib/src/main/java/com/signnow/library/dto/Document.java b/client-lib/src/main/java/com/signnow/library/dto/Document.java index b442028..409b987 100644 --- a/client-lib/src/main/java/com/signnow/library/dto/Document.java +++ b/client-lib/src/main/java/com/signnow/library/dto/Document.java @@ -2,8 +2,7 @@ import com.fasterxml.jackson.annotation.*; -import java.util.ArrayList; -import java.util.List; +import java.util.*; @JsonIgnoreProperties(ignoreUnknown = true) public class Document extends GenericId { @@ -28,6 +27,9 @@ public class Document extends GenericId { * Free form invites info */ public List requests; + @JsonProperty("fields") + public List fields; + public List roles; public static class SigningLinkRequest { @JsonProperty("document_id") @@ -127,6 +129,14 @@ public String toString() { } } + public static class PrefillTextRequest { + public final List fields; + + public PrefillTextRequest(List fields) { + this.fields = fields; + } + } + public static class FieldsUpdateRequest { public final List fields; @@ -135,6 +145,19 @@ public FieldsUpdateRequest(List fields) { } } + @JsonInclude(JsonInclude.Include.NON_NULL) + public static class FieldText { + @JsonProperty("field_name") + public String fieldName; + @JsonProperty("prefilled_text") + public String prefilledText; + + public FieldText(String name, String prefill) { + this.fieldName = name; + this.prefilledText = prefill; + } + } + @JsonInclude(JsonInclude.Include.NON_NULL) public static class Field { public int x; @@ -155,11 +178,70 @@ public String getType() { } } + @JsonInclude(JsonInclude.Include.NON_NULL) + public static class FieldMetadata { + public String id; + public String type; + @JsonProperty("role_id") + public String roleId; + public String role; + public String originator; + public String fulfiller; + @JsonProperty("json_attributes") + public Attributes attributes; + @JsonProperty("field_request_id") + public String fieldRequestId; + @JsonProperty("element_id") + public String elementId; + @JsonProperty("field_request_canceled") + public boolean fieldRequestCanceled; + @JsonProperty("template_field_id") + public String templateFieldId; + @JsonProperty("field_id") + public String fieldId; + + public String getType() { + return type.toString(); + } + + @JsonInclude(JsonInclude.Include.NON_NULL) + @JsonIgnoreProperties(ignoreUnknown = true) + public static class Attributes { + @JsonProperty("page_number") + public String pageNumber; + public int x; + public int y; + public int width; + public int height; + public boolean required; + public String name; + public String label; + @JsonProperty("prefilled_text") + public String prefilledText; + } + } + @JsonIgnoreProperties(ignoreUnknown = true) public static class FieldInvite extends GenericId { public String status; public String email; public String role; + @JsonProperty("role_id") + public String roleId; + @JsonProperty("is_embedded") + public Boolean isEmbedded; + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static class Role { + @JsonProperty("unique_id") + public String uniqueId; + /** + * SignNow returns this number as a string, same as {@link Document#pageCount} + */ + @JsonProperty("signing_order") + public String signingOrder; + public String name; } @JsonIgnoreProperties(ignoreUnknown = true) @@ -175,4 +257,78 @@ public static class DocumentDownloadLink { public String link; } + /*------------------------Embedded invites------------------------*/ + public static class EmbeddedSigningInviteRequest { + public final List invites = new ArrayList<>(); + + public EmbeddedSigningInviteRequest(EmbeddedInvite... invite) { + this.invites.addAll(Arrays.asList(invite)); + } + } + + @JsonInclude(JsonInclude.Include.NON_NULL) + public static class EmbeddedInvite { + public String email; + public String role_id; + public String role; + public int order = 1; + @JsonProperty("auth_method") + public String authMethod = "none"; + @JsonProperty("first_name") + public String firstName; + @JsonProperty("last_name") + public String lastName; + public String language; + + /** + * @param email signer email + * @param role role NAME (e.g. "Signer 1"), not the role id. To create an invite + * by role id use {@link #forRoleId(String, String)}. + */ + public EmbeddedInvite(String email, String role) { + this.email = email; + this.role = role; + } + + /** + * Creates an invite addressed by role id (the {@code unique_id} of a {@link Role}). + * + * @param email signer email + * @param roleId role id from {@link Document#roles} ({@link Role#uniqueId}) + */ + public static EmbeddedInvite forRoleId(String email, String roleId) { + EmbeddedInvite invite = new EmbeddedInvite(email, null); + invite.role_id = roleId; + return invite; + } + } + + public static class EmbeddedSigningInviteResponse { + public List data = new ArrayList<>(); + } + + @JsonInclude(JsonInclude.Include.NON_NULL) + public static class InviteResponseData { + public String id; + public String email; + @JsonProperty("role_id") + public String roleId; + public int order; + public String status; + } + + public static class EmbeddedInviteLinkRequest { + @JsonProperty("link_expiration") + public int linkExpiration = 45; + @JsonProperty("auth_method") + public String authMethod = "none"; + } + + public static class EmbeddedInviteLinkResponse { + public Map data = new HashMap<>(); + + public String getLink() { + return data.get("link"); + } + } } diff --git a/client-lib/src/main/java/com/signnow/library/dto/DocumentGroup.java b/client-lib/src/main/java/com/signnow/library/dto/DocumentGroup.java index 33f4af0..e7a69e7 100644 --- a/client-lib/src/main/java/com/signnow/library/dto/DocumentGroup.java +++ b/client-lib/src/main/java/com/signnow/library/dto/DocumentGroup.java @@ -1,11 +1,15 @@ package com.signnow.library.dto; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonSetter; import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; import java.util.List; +import java.util.Map; @JsonIgnoreProperties(ignoreUnknown = true) public class DocumentGroup extends GenericId { @@ -57,4 +61,145 @@ public static class DocumentGroupsListResponce { @JsonProperty("document_group_total_count") public Integer totalCount; } + + /*------------------------Embedded group invites------------------------*/ + + @JsonInclude(JsonInclude.Include.NON_NULL) + public static class EmbeddedGroupInviteRequest { + public final List invites; + /** + * When true the signer gets one merged signing session for the whole group + */ + @JsonProperty("sign_as_merged") + public Boolean signAsMerged = Boolean.TRUE; + + public EmbeddedGroupInviteRequest(EmbeddedGroupInviteStep... steps) { + this.invites = new ArrayList<>(Arrays.asList(steps)); + } + } + + @JsonInclude(JsonInclude.Include.NON_NULL) + public static class EmbeddedGroupInviteStep { + public final int order; + public final List signers; + + public EmbeddedGroupInviteStep(int order, EmbeddedGroupInviteSigner... signers) { + this.order = order; + this.signers = new ArrayList<>(Arrays.asList(signers)); + } + } + + @JsonInclude(JsonInclude.Include.NON_NULL) + public static class EmbeddedGroupInviteSigner { + public String email; + @JsonProperty("auth_method") + public String authMethod = "none"; + @JsonProperty("first_name") + public String firstName; + @JsonProperty("last_name") + public String lastName; + public String language; + public final List documents; + + public EmbeddedGroupInviteSigner(String email, EmbeddedGroupInviteDocument... documents) { + this.email = email; + this.documents = new ArrayList<>(Arrays.asList(documents)); + } + } + + @JsonInclude(JsonInclude.Include.NON_NULL) + public static class EmbeddedGroupInviteDocument { + public final String id; + public String action = "sign"; + public final String role; + + /** + * @param documentId id of a document belonging to the group + * @param role role NAME (e.g. "Signer 1", as listed in {@link DocumentInfo#roles}), + * NOT a role id — unlike the per-document embedded invite API + */ + public EmbeddedGroupInviteDocument(String documentId, String role) { + this.id = documentId; + this.role = role; + } + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static class EmbeddedGroupInviteResponse { + public InviteData data; + + public String getId() { + return data != null ? data.id : null; + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static class InviteData { + public String id; + } + } + + @JsonInclude(JsonInclude.Include.NON_NULL) + public static class EmbeddedGroupInviteLinkRequest { + /** + * Identifies the signer the link is minted for (unlike the per-document link + * mint, which addresses the signer in the URL) + */ + public String email; + @JsonProperty("auth_method") + public String authMethod = "none"; + @JsonProperty("link_expiration") + public Integer linkExpiration = 45; + + public EmbeddedGroupInviteLinkRequest(String email) { + this.email = email; + } + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static class EmbeddedGroupInviteLinkResponse { + public Map data = new HashMap<>(); + + public String getLink() { + return data.get("link"); + } + } + + /*------------------------Document group info v2------------------------*/ + + @JsonIgnoreProperties(ignoreUnknown = true) + public static class DocumentGroupV2Response { + public DocumentGroupV2Info data; + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static class DocumentGroupV2Info { + public String id; + public String name; + @JsonProperty("invite_id") + public String inviteId; + @JsonProperty("pending_step_id") + public String pendingStepId; + public String state; + @JsonProperty("last_invite_id") + public String lastInviteId; + public List documents = new ArrayList<>(); + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static class DocumentGroupV2Document { + public String id; + public List roles = new ArrayList<>(); + @JsonProperty("document_name") + public String documentName; + @JsonProperty("field_invites") + public List fieldInvites = new ArrayList<>(); + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static class DocumentGroupV2FieldInvite { + public String id; + public String status; + @JsonProperty("signer_email") + public String signerEmail; + } } diff --git a/client-lib/src/main/java/com/signnow/library/dto/Webhook.java b/client-lib/src/main/java/com/signnow/library/dto/Webhook.java new file mode 100644 index 0000000..a11115d --- /dev/null +++ b/client-lib/src/main/java/com/signnow/library/dto/Webhook.java @@ -0,0 +1,37 @@ +package com.signnow.library.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.Map; + +public class Webhook { + public static class CreateRequest { + @JsonProperty("event") + public final String event; + @JsonProperty("entity_id") + public final String entityId; + @JsonProperty("action") + public final String action = "callback"; + @JsonProperty("attributes") + public final Attributes attributes; + + public CreateRequest(String event, String entityId, String callback, Map headers) { + this.event = event; + this.entityId = entityId; + this.attributes = new Attributes(callback, headers); + } + } + + static class Attributes { + @JsonProperty("callback") + public final String callback; + + @JsonProperty("headers") + public final Map headers; + + Attributes(String callback, Map headers) { + this.callback = callback; + this.headers = headers; + } + } +} diff --git a/client-lib/src/main/java/com/signnow/library/facades/DocumentGroups.java b/client-lib/src/main/java/com/signnow/library/facades/DocumentGroups.java index 5a2a35d..45280fb 100644 --- a/client-lib/src/main/java/com/signnow/library/facades/DocumentGroups.java +++ b/client-lib/src/main/java/com/signnow/library/facades/DocumentGroups.java @@ -21,4 +21,12 @@ public interface DocumentGroups { String createDocumentGroupInvite(String documentGroupId, GroupInvite groupInvite) throws SNException; void resendInvites(String documentGroupId, String inviteId, String email) throws SNException; + + DocumentGroup.EmbeddedGroupInviteResponse createDocumentGroupEmbeddedInvite(String documentGroupId, DocumentGroup.EmbeddedGroupInviteRequest request) throws SNException; + + String getDocumentGroupEmbeddedInviteLink(String documentGroupId, String embeddedInviteId, DocumentGroup.EmbeddedGroupInviteLinkRequest request) throws SNException; + + DocumentGroup.DocumentGroupV2Response getDocumentGroupV2(String documentGroupId) throws SNException; + + void deleteDocumentGroupEmbeddedInvite(String documentGroupId) throws SNException; } diff --git a/client-lib/src/main/java/com/signnow/library/facades/Documents.java b/client-lib/src/main/java/com/signnow/library/facades/Documents.java index a1a6539..b496375 100644 --- a/client-lib/src/main/java/com/signnow/library/facades/Documents.java +++ b/client-lib/src/main/java/com/signnow/library/facades/Documents.java @@ -22,11 +22,20 @@ public interface Documents { void sendDocumentSignInvite(String documentId, Document.SigningInviteWithRolesRequest request) throws SNException; + Document.EmbeddedSigningInviteResponse createDocumentEmbeddedSignInvite(String documentId, Document.EmbeddedSigningInviteRequest request) throws SNException; + + String getDocumentEmbeddedSignInviteLink(String documentId, String inviteId, Document.EmbeddedInviteLinkRequest request) throws SNException; + + void deleteDocumentEmbeddedSignInvite(String documentId) throws SNException; + void updateDocumentFields(String documentId, List request) throws SNException; + void prefillText(String documentId, List request) throws SNException; + Document getDocument(String documentId) throws SNException; void deleteDocument(String documentId) throws SNException; String getDownloadLink(String documentId) throws SNException; + } diff --git a/client-lib/src/main/java/com/signnow/library/facades/ServiceProvider.java b/client-lib/src/main/java/com/signnow/library/facades/ServiceProvider.java index 0e979cb..2edf25c 100644 --- a/client-lib/src/main/java/com/signnow/library/facades/ServiceProvider.java +++ b/client-lib/src/main/java/com/signnow/library/facades/ServiceProvider.java @@ -6,4 +6,6 @@ public interface ServiceProvider { Templates templatesService(); DocumentGroups documentGroupsService(); + + Webhooks webhooksService(); } diff --git a/client-lib/src/main/java/com/signnow/library/facades/Webhooks.java b/client-lib/src/main/java/com/signnow/library/facades/Webhooks.java new file mode 100644 index 0000000..b790b4e --- /dev/null +++ b/client-lib/src/main/java/com/signnow/library/facades/Webhooks.java @@ -0,0 +1,10 @@ +package com.signnow.library.facades; + +import com.signnow.library.exceptions.SNException; + +import java.util.Map; + +public interface Webhooks { + void add(String event, String entityId, String callback, Map headers) throws SNException; + void delete(String webhookId) throws SNException; +} diff --git a/client-lib/src/main/java/com/signnow/library/services/DocumentGroupsService.java b/client-lib/src/main/java/com/signnow/library/services/DocumentGroupsService.java index 971c601..c50c71f 100644 --- a/client-lib/src/main/java/com/signnow/library/services/DocumentGroupsService.java +++ b/client-lib/src/main/java/com/signnow/library/services/DocumentGroupsService.java @@ -91,4 +91,43 @@ public void resendInvites(String documentGroupId, String inviteId, String email) GenericId.class ); } + + @Override + public DocumentGroup.EmbeddedGroupInviteResponse createDocumentGroupEmbeddedInvite(String documentGroupId, DocumentGroup.EmbeddedGroupInviteRequest request) throws SNException { + return client.post( + "/v2/document-groups/{document_group_id}/embedded-invites", + Collections.singletonMap("document_group_id", documentGroupId), + request, + DocumentGroup.EmbeddedGroupInviteResponse.class + ); + } + + @Override + public String getDocumentGroupEmbeddedInviteLink(String documentGroupId, String embeddedInviteId, DocumentGroup.EmbeddedGroupInviteLinkRequest request) throws SNException { + Map params = new HashMap<>(); + params.put("document_group_id", documentGroupId); + params.put("embedded_invite_id", embeddedInviteId); + DocumentGroup.EmbeddedGroupInviteLinkResponse response = client.post( + "/v2/document-groups/{document_group_id}/embedded-invites/{embedded_invite_id}/link", + params, request, DocumentGroup.EmbeddedGroupInviteLinkResponse.class); + return response.getLink(); + } + + @Override + public DocumentGroup.DocumentGroupV2Response getDocumentGroupV2(String documentGroupId) throws SNException { + return client.get( + "/v2/document-groups/{document_group_id}", + Collections.singletonMap("document_group_id", documentGroupId), + DocumentGroup.DocumentGroupV2Response.class + ); + } + + @Override + public void deleteDocumentGroupEmbeddedInvite(String documentGroupId) throws SNException { + client.delete( + "/v2/document-groups/{document_group_id}/embedded-invites", + Collections.singletonMap("document_group_id", documentGroupId), + String.class + ); + } } diff --git a/client-lib/src/main/java/com/signnow/library/services/DocumentsService.java b/client-lib/src/main/java/com/signnow/library/services/DocumentsService.java index 551f64f..8465a2a 100644 --- a/client-lib/src/main/java/com/signnow/library/services/DocumentsService.java +++ b/client-lib/src/main/java/com/signnow/library/services/DocumentsService.java @@ -14,7 +14,9 @@ import javax.ws.rs.core.Response; import java.io.InputStream; import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.Map; public class DocumentsService extends ApiService implements Documents { public DocumentsService(SNClient client) { @@ -72,6 +74,45 @@ public void sendDocumentSignInvite(String documentId, Document.SigningInviteWith ); } + @Override + public Document.EmbeddedSigningInviteResponse createDocumentEmbeddedSignInvite(String documentId, Document.EmbeddedSigningInviteRequest request) throws SNException { + return client.post( + "/v2/documents/{document_id}/embedded-invites", + Collections.singletonMap("document_id", documentId), + request, + Document.EmbeddedSigningInviteResponse.class); + } + + @Override + public String getDocumentEmbeddedSignInviteLink(String documentId, String inviteId, Document.EmbeddedInviteLinkRequest request) throws SNException { + Map params = new HashMap<>(); + params.put("document_id", documentId); + params.put("fieldInviteUniqueId", inviteId); + Document.EmbeddedInviteLinkResponse response = client.post( + "/v2/documents/{document_id}/embedded-invites/{fieldInviteUniqueId}/link", + params, request, Document.EmbeddedInviteLinkResponse.class); + return response.getLink(); + } + + @Override + public void deleteDocumentEmbeddedSignInvite(String documentId) throws SNException { + client.delete( + "/v2/documents/{document_id}/embedded-invites", + Collections.singletonMap("document_id", documentId), + String.class + ); + } + + @Override + public void prefillText(String documentId, List request) throws SNException { + client.put( + "/v2/documents/{documentId}/prefill-texts", + Collections.singletonMap("documentId", documentId), + new Document.PrefillTextRequest(request), + String.class + ); + } + @Override public void updateDocumentFields(String documentId, List request) throws SNException { client.put( @@ -109,4 +150,5 @@ public String getDownloadLink(String documentId) throws SNException { Document.DocumentDownloadLink.class ).link; } + } diff --git a/client-lib/src/main/java/com/signnow/library/services/WebhookService.java b/client-lib/src/main/java/com/signnow/library/services/WebhookService.java new file mode 100644 index 0000000..4931ae2 --- /dev/null +++ b/client-lib/src/main/java/com/signnow/library/services/WebhookService.java @@ -0,0 +1,35 @@ +package com.signnow.library.services; + +import com.signnow.library.SNClient; +import com.signnow.library.dto.Webhook; +import com.signnow.library.exceptions.SNException; +import com.signnow.library.facades.Webhooks; + +import java.util.Collections; +import java.util.Map; + +public class WebhookService extends ApiService implements Webhooks { + + public WebhookService(SNClient client) { + super(client); + } + + @Override + public void add(String event, String entityId, String callback, Map headers) throws SNException { + client.post( + "/api/v2/events", + null, + new Webhook.CreateRequest(event, entityId, callback, headers), + String.class + ); + } + + @Override + public void delete(String eventSubscriptionId) throws SNException { + client.delete( + "/api/v2/events/{eventSubscriptionId}", + Collections.singletonMap("eventSubscriptionId", eventSubscriptionId), + String.class + ); + } +} diff --git a/client-lib/src/test/java/com/signnow/library/dto/DocumentGroupEmbeddedInviteSerializationTest.java b/client-lib/src/test/java/com/signnow/library/dto/DocumentGroupEmbeddedInviteSerializationTest.java new file mode 100644 index 0000000..66e01e6 --- /dev/null +++ b/client-lib/src/test/java/com/signnow/library/dto/DocumentGroupEmbeddedInviteSerializationTest.java @@ -0,0 +1,110 @@ +package com.signnow.library.dto; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * Wire-format tests for the embedded group invite DTOs: request bodies must carry + * the exact snake_case keys with nulls omitted, responses must tolerate unknown keys + * (the mapper below fails on unknown properties, like the client's Jackson provider). + */ +class DocumentGroupEmbeddedInviteSerializationTest { + private final ObjectMapper mapper = new ObjectMapper(); + + @Test + void createRequestSerializesExactKeys() throws Exception { + DocumentGroup.EmbeddedGroupInviteDocument document = + new DocumentGroup.EmbeddedGroupInviteDocument("doc123", "Signer 1"); + DocumentGroup.EmbeddedGroupInviteSigner signer = + new DocumentGroup.EmbeddedGroupInviteSigner("signer@example.com", document); + signer.firstName = "John"; + signer.lastName = "Doe"; + signer.language = "en"; + DocumentGroup.EmbeddedGroupInviteRequest request = new DocumentGroup.EmbeddedGroupInviteRequest( + new DocumentGroup.EmbeddedGroupInviteStep(1, signer)); + + JsonNode actual = mapper.readTree(mapper.writeValueAsString(request)); + JsonNode expected = mapper.readTree("{" + + "\"invites\":[{\"order\":1,\"signers\":[{" + + "\"email\":\"signer@example.com\",\"auth_method\":\"none\"," + + "\"first_name\":\"John\",\"last_name\":\"Doe\",\"language\":\"en\"," + + "\"documents\":[{\"id\":\"doc123\",\"action\":\"sign\",\"role\":\"Signer 1\"}]" + + "}]}]," + + "\"sign_as_merged\":true}"); + + assertEquals(expected, actual); + } + + @Test + void createRequestOmitsNullOptionalFields() throws Exception { + DocumentGroup.EmbeddedGroupInviteRequest request = new DocumentGroup.EmbeddedGroupInviteRequest( + new DocumentGroup.EmbeddedGroupInviteStep(1, + new DocumentGroup.EmbeddedGroupInviteSigner("signer@example.com", + new DocumentGroup.EmbeddedGroupInviteDocument("doc123", "Signer 1")))); + + JsonNode signer = mapper.readTree(mapper.writeValueAsString(request)) + .get("invites").get(0).get("signers").get(0); + + assertFalse(signer.has("first_name")); + assertFalse(signer.has("last_name")); + assertFalse(signer.has("language")); + } + + @Test + void linkRequestSerializesExactKeys() throws Exception { + DocumentGroup.EmbeddedGroupInviteLinkRequest request = + new DocumentGroup.EmbeddedGroupInviteLinkRequest("signer@example.com"); + + JsonNode actual = mapper.readTree(mapper.writeValueAsString(request)); + JsonNode expected = mapper.readTree( + "{\"email\":\"signer@example.com\",\"auth_method\":\"none\",\"link_expiration\":45}"); + + assertEquals(expected, actual); + } + + @Test + void createResponseToleratesUnknownKeys() throws Exception { + DocumentGroup.EmbeddedGroupInviteResponse response = mapper.readValue( + "{\"data\":{\"id\":\"invite123\",\"unexpected\":\"x\"},\"extra\":1}", + DocumentGroup.EmbeddedGroupInviteResponse.class); + + assertEquals("invite123", response.getId()); + } + + @Test + void linkResponseToleratesUnknownKeys() throws Exception { + DocumentGroup.EmbeddedGroupInviteLinkResponse response = mapper.readValue( + "{\"data\":{\"link\":\"https://example.com/sign\"},\"extra\":\"x\"}", + DocumentGroup.EmbeddedGroupInviteLinkResponse.class); + + assertEquals("https://example.com/sign", response.getLink()); + } + + @Test + void groupInfoV2ResponseToleratesUnknownKeys() throws Exception { + DocumentGroup.DocumentGroupV2Response response = mapper.readValue("{\"data\":{" + + "\"id\":\"group123\",\"name\":\"Bundle\",\"created\":1712233114," + + "\"invite_id\":null,\"pending_step_id\":\"step1\",\"state\":\"pending\"," + + "\"last_invite_id\":\"inv1\",\"folder_id\":\"f1\"," + + "\"documents\":[{\"id\":\"doc1\",\"roles\":[\"Signer 1\"]," + + "\"document_name\":\"Contract\",\"updated\":1712233114," + + "\"field_invites\":[{\"id\":\"fi1\",\"status\":\"pending\"," + + "\"signer_email\":\"signer@example.com\",\"created\":1712233114}]}]," + + "\"freeform_invite\":{\"id\":null,\"last_id\":null}}}", + DocumentGroup.DocumentGroupV2Response.class); + + assertEquals("group123", response.data.id); + assertNull(response.data.inviteId); + assertEquals("pending", response.data.state); + assertEquals("doc1", response.data.documents.get(0).id); + assertTrue(response.data.documents.get(0).roles.contains("Signer 1")); + assertEquals("pending", response.data.documents.get(0).fieldInvites.get(0).status); + assertEquals("signer@example.com", response.data.documents.get(0).fieldInvites.get(0).signerEmail); + } +} diff --git a/client-lib/src/test/java/com/signnow/library/services/DocumentGroupsServiceTest.java b/client-lib/src/test/java/com/signnow/library/services/DocumentGroupsServiceTest.java index ab4b9aa..4b8c9f0 100644 --- a/client-lib/src/test/java/com/signnow/library/services/DocumentGroupsServiceTest.java +++ b/client-lib/src/test/java/com/signnow/library/services/DocumentGroupsServiceTest.java @@ -118,6 +118,68 @@ void createDocumentGroupInvite() throws SNException { , eq(GenericId.class)); } + @Test + void createDocumentGroupEmbeddedInvite() throws SNException { + DocumentGroup.EmbeddedGroupInviteRequest requestMock = mock(DocumentGroup.EmbeddedGroupInviteRequest.class); + final String path = "/v2/document-groups/{document_group_id}/embedded-invites"; + when(clientMock.post(eq(path) + , anyMap() + , eq(requestMock) + , eq(DocumentGroup.EmbeddedGroupInviteResponse.class)) + ).thenReturn(new DocumentGroup.EmbeddedGroupInviteResponse()); + + service.createDocumentGroupEmbeddedInvite("1", requestMock); + + verify(clientMock, times(1)).post( + eq(path) + , anyMap() + , eq(requestMock) + , eq(DocumentGroup.EmbeddedGroupInviteResponse.class)); + } + + @Test + void getDocumentGroupEmbeddedInviteLink() throws SNException { + DocumentGroup.EmbeddedGroupInviteLinkRequest request = new DocumentGroup.EmbeddedGroupInviteLinkRequest("signer@example.com"); + DocumentGroup.EmbeddedGroupInviteLinkResponse response = new DocumentGroup.EmbeddedGroupInviteLinkResponse(); + response.data.put("link", "https://link"); + final String path = "/v2/document-groups/{document_group_id}/embedded-invites/{embedded_invite_id}/link"; + when(clientMock.post(eq(path) + , anyMap() + , eq(request) + , eq(DocumentGroup.EmbeddedGroupInviteLinkResponse.class)) + ).thenReturn(response); + + String link = service.getDocumentGroupEmbeddedInviteLink("1", "2", request); + + assertEquals("https://link", link); + verify(clientMock, times(1)).post( + eq(path) + , anyMap() + , eq(request) + , eq(DocumentGroup.EmbeddedGroupInviteLinkResponse.class)); + } + + @Test + void getDocumentGroupV2() throws SNException { + final String path = "/v2/document-groups/{document_group_id}"; + when(clientMock.get(eq(path), anyMap(), eq(DocumentGroup.DocumentGroupV2Response.class))) + .thenReturn(new DocumentGroup.DocumentGroupV2Response()); + + service.getDocumentGroupV2("1"); + + verify(clientMock, times(1)).get(eq(path), anyMap(), eq(DocumentGroup.DocumentGroupV2Response.class)); + } + + @Test + void deleteDocumentGroupEmbeddedInvite() throws SNException { + final String path = "/v2/document-groups/{document_group_id}/embedded-invites"; + when(clientMock.delete(eq(path), anyMap(), eq(String.class))).thenReturn("{}"); + + service.deleteDocumentGroupEmbeddedInvite("1"); + + verify(clientMock, times(1)).delete(eq(path), anyMap(), eq(String.class)); + } + @Test void resendInvites() throws SNException { final String path = "/documentgroup/{documentGroupId}/groupinvite/{inviteId}/resendinvites"; diff --git a/client-lib/src/test/java/com/signnow/library/services/DocumentsServiceTest.java b/client-lib/src/test/java/com/signnow/library/services/DocumentsServiceTest.java index 0ba6b08..5247fab 100644 --- a/client-lib/src/test/java/com/signnow/library/services/DocumentsServiceTest.java +++ b/client-lib/src/test/java/com/signnow/library/services/DocumentsServiceTest.java @@ -146,6 +146,15 @@ void deleteDocument() throws SNException { verify(clientMock, times(1)).delete(anyString(), anyMap(), any()); } + @Test + void deleteDocumentEmbeddedSignInvite() throws SNException { + when(clientMock.delete(anyString(), anyMap(), any())).thenReturn(null); + + service.deleteDocumentEmbeddedSignInvite("1"); + + verify(clientMock, times(1)).delete(anyString(), anyMap(), any()); + } + @Test void getDownloadLink() throws SNException { when(clientMock.post(anyString()