apacuana-sdk-core 0.11.0 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/README.md +131 -164
  2. package/coverage/clover.xml +338 -302
  3. package/coverage/coverage-final.json +9 -9
  4. package/coverage/lcov-report/index.html +31 -31
  5. package/coverage/lcov-report/src/api/certs.js.html +1 -1
  6. package/coverage/lcov-report/src/api/faceLiveness.js.html +34 -7
  7. package/coverage/lcov-report/src/api/index.html +13 -13
  8. package/coverage/lcov-report/src/api/revocations.js.html +50 -5
  9. package/coverage/lcov-report/src/api/signatures.js.html +17 -56
  10. package/coverage/lcov-report/src/api/users.js.html +20 -5
  11. package/coverage/lcov-report/src/config/index.html +1 -1
  12. package/coverage/lcov-report/src/config/index.js.html +1 -1
  13. package/coverage/lcov-report/src/errors/index.html +1 -1
  14. package/coverage/lcov-report/src/errors/index.js.html +8 -8
  15. package/coverage/lcov-report/src/index.html +13 -13
  16. package/coverage/lcov-report/src/index.js.html +59 -14
  17. package/coverage/lcov-report/src/success/index.html +1 -1
  18. package/coverage/lcov-report/src/success/index.js.html +5 -5
  19. package/coverage/lcov-report/src/utils/constant.js.html +1 -1
  20. package/coverage/lcov-report/src/utils/helpers.js.html +277 -16
  21. package/coverage/lcov-report/src/utils/httpClient.js.html +32 -17
  22. package/coverage/lcov-report/src/utils/index.html +22 -22
  23. package/coverage/lcov.info +641 -561
  24. package/dist/api/revocations.d.ts +6 -3
  25. package/dist/api/users.d.ts +16 -6
  26. package/dist/index.d.ts +0 -2
  27. package/dist/index.js +149 -147
  28. package/dist/index.js.map +1 -1
  29. package/dist/index.mjs +149 -147
  30. package/dist/index.mjs.map +1 -1
  31. package/dist/types/faceLiveness.d.ts +6 -5
  32. package/dist/types/revocations.d.ts +26 -15
  33. package/dist/types/signatures.d.ts +73 -63
  34. package/dist/utils/helpers.d.ts +4 -2
  35. package/package.json +1 -1
  36. package/src/api/faceLiveness.js +13 -4
  37. package/src/api/revocations.js +18 -3
  38. package/src/api/signatures.js +8 -21
  39. package/src/api/users.js +8 -3
  40. package/src/index.js +22 -7
  41. package/src/types/faceLiveness.js +11 -2
  42. package/src/types/revocations.js +29 -9
  43. package/src/types/signatures.js +47 -28
  44. package/src/utils/helpers.js +95 -8
  45. package/src/utils/httpClient.js +15 -10
  46. package/tests/api/faceLiveness.test.js +28 -30
  47. package/tests/api/revocations.test.js +4 -4
  48. package/tests/api/signatures.test.js +73 -16
  49. package/tests/index.test.js +16 -8
@@ -1,3 +1,7 @@
1
+ /**
2
+ * @typedef {import('../success').ApacuanaSuccess} ApacuanaSuccess
3
+ */
4
+
1
5
  /**
2
6
  * Define la estructura de un objeto Firmante.
3
7
  * @typedef {object} Signer
@@ -7,30 +11,54 @@
7
11
  */
8
12
 
9
13
  /**
10
- * Define la estructura de datos para añadir un firmante.
11
- * @typedef {object} SignerData
12
- * @property {string} docId - Identificador único del documento.
14
+ * @typedef {object} AddSignerData
15
+ * @property {string} signer - Identificador de confirmación del firmante añadido.
13
16
  */
14
17
 
15
18
  /**
16
19
  * Define la estructura de la respuesta al añadir un firmante.
17
20
  * @typedef {object} AddSignerResponse
18
- * @property {string} signer - Identificador de confirmación del firmante añadido.
19
- * @property {boolean} success - Indica si la operación fue exitosa.
21
+ * @property {true} success
22
+ * @property {AddSignerData} data
20
23
  */
21
24
 
22
25
  /**
23
26
  * Define la estructura de datos para obtener el digest de un documento.
24
27
  * @typedef {object} GetDigestData
25
28
  * @property {string} cert - Certificado del firmante en formato base64.
26
- * @property {string} signatureId - Identificador único del proceso de firma.
29
+ * @property {string} signatureId - Identificador único de la firma.
30
+ * @property {File} [document] - Documento a firmar en formato de archivo. Este campo es opcional.
31
+ */
32
+
33
+ /**
34
+ * @typedef {object} SignaturePlacement
35
+ * @property {number} page - Página donde se estampará la firma.
36
+ * @property {number} x - Coordenada X de la firma.
37
+ * @property {number} y - Coordenada Y de la firma.
38
+ */
39
+
40
+ /**
41
+ * Define la estructura de datos para añadir un firmante.
42
+ * @typedef {object} SignerData
43
+ * @property {string} name - Nombre del firmante.
44
+ * @property {string} typedoc - Tipo de documento de identidad del firmante.
45
+ * @property {string} doc - Número de documento de identidad del firmante.
46
+ * @property {Array<SignaturePlacement>} signature - Array con las coordenadas donde se estampará la firma.
47
+ * @property {string} [reference] - Referencia a un documento pre-cargado. Se debe proporcionar 'reference' o 'file', pero no ambos.
48
+ * @property {File} [file] - Archivo del documento a firmar. Se debe proporcionar 'reference' o 'file', pero no ambos.
49
+ */
50
+
51
+ /**
52
+ * Define la estructura de datos para la respuesta de la creación de una firma.
53
+ * @typedef {object} GetDigestResponseData
54
+ * @property {string} digest - El digest del documento que se va a firmar.
27
55
  */
28
56
 
29
57
  /**
30
58
  * Define la estructura de la respuesta al obtener el digest.
31
59
  * @typedef {object} GetDigestResponse
32
- * @property {string} digest - El digest del documento que se va a firmar.
33
- * @property {boolean} success - Indica si la operación fue exitosa.
60
+ * @property {true} success
61
+ * @property {GetDigestResponseData} data
34
62
  */
35
63
 
36
64
  /**
@@ -42,11 +70,16 @@
42
70
  */
43
71
 
44
72
  /**
45
- * Define la estructura de la respuesta al obtener documentos.
46
- * @typedef {object} GetDocsResponse
73
+ * @typedef {object} GetDocsResponseData
47
74
  * @property {number} totalRecords - El número total de registros encontrados.
48
75
  * @property {Array<object>} records - Un arreglo con los registros de los documentos.
49
- * @property {boolean} success - Indica si la operación fue exitosa.
76
+ */
77
+
78
+ /**
79
+ * Define la estructura de la respuesta al obtener documentos.
80
+ * @typedef {object} GetDocsResponse
81
+ * @property {true} success
82
+ * @property {GetDocsResponseData} data
50
83
  */
51
84
 
52
85
  /**
@@ -56,22 +89,8 @@
56
89
  * @property {Array<object>} signature.positions - Posiciones de la firma.
57
90
  * @property {string} cert - Certificado en base64.
58
91
  * @property {string} signedDigest - Digest firmado.
92
+ * @property {File} [document] - Documento a firmar en formato de archivo. Este campo es opcional.
93
+
59
94
  */
60
95
 
61
- /**
62
- * @typedef {object} SignaturePosition
63
- * @property {number} page - The page number for the signature.
64
- * @property {number} x - The x-coordinate for the signature's position (from 0 to 1).
65
- * @property {number} y - The y-coordinate for the signature's position (from 0 to 1).
66
- */
67
-
68
- /**
69
- * @typedef {object} OnboardingSignerData
70
- * @property {string} name - The name of the document.
71
- * @property {string} reference - An external reference for the document.
72
- * @property {string} typedoc - The type of document of the signer (e.g., "V", "E", "J").
73
- * @property {string} doc - The document number of the signer.
74
- * @property {SignaturePosition[]} signature - An array of signature positions.
75
- */
76
-
77
- export {};
96
+ export {};
@@ -65,8 +65,31 @@ const encryptedCsr = (csr, encryptKey = KEY_HEX) => {
65
65
  return body;
66
66
  };
67
67
 
68
+ const createPayloadGetDigest = (signData) => {
69
+ const formData = new FormData();
70
+
71
+ // Iterate over the object's own properties
72
+ Object.keys(signData).forEach((key) => {
73
+ // Conditionally handle the 'document' field
74
+
75
+ if (key === "document" && signData[key] instanceof File) {
76
+ // Append the file directly
77
+ formData.append(key, signData[key]);
78
+ } else if (key === "cert") {
79
+ formData.append("publickey", signData[key]);
80
+ }
81
+ });
82
+ return formData;
83
+ };
84
+
68
85
  const validateOnBoardingSignerData = (signerData) => {
69
- if (!signerData.name || typeof signerData.name !== "string") {
86
+ // Create a copy of the original object to avoid modifying the function parameter
87
+ const validatedSignerData = { ...signerData };
88
+
89
+ if (
90
+ !validatedSignerData.name ||
91
+ typeof validatedSignerData.name !== "string"
92
+ ) {
70
93
  throw new ApacuanaAPIError(
71
94
  'El campo "name" es requerido y debe ser una cadena de texto.',
72
95
  400,
@@ -74,16 +97,46 @@ const validateOnBoardingSignerData = (signerData) => {
74
97
  );
75
98
  }
76
99
 
77
- if (!signerData.reference || typeof signerData.reference !== "string") {
100
+ // Limpia 'reference' si es inválido o está vacío.
101
+ if (
102
+ !validatedSignerData.reference ||
103
+ typeof validatedSignerData.reference !== "string" ||
104
+ validatedSignerData.reference.trim() === ""
105
+ ) {
106
+ delete validatedSignerData.reference;
107
+ }
108
+
109
+ // Limpia 'file' si no es una instancia de File.
110
+ if (
111
+ typeof File === "undefined" ||
112
+ !validatedSignerData.file ||
113
+ !(validatedSignerData.file instanceof File)
114
+ ) {
115
+ delete validatedSignerData.file;
116
+ }
117
+
118
+ // Valida que solo uno de los dos campos exista después de la limpieza.
119
+ if (validatedSignerData.reference && validatedSignerData.file) {
120
+ throw new ApacuanaAPIError(
121
+ "No se pueden proporcionar 'reference' y 'file' simultáneamente.",
122
+ 400,
123
+ "INVALID_PARAMETER_FORMAT"
124
+ );
125
+ }
126
+
127
+ if (!validatedSignerData.reference && !validatedSignerData.file) {
78
128
  throw new ApacuanaAPIError(
79
- 'El campo "reference" es requerido y debe ser una cadena de texto.',
129
+ "Se debe proporcionar un campo 'reference' o 'file' válido.",
80
130
  400,
81
131
  "INVALID_PARAMETER_FORMAT"
82
132
  );
83
133
  }
84
134
 
85
135
  const validDocTypes = ["V", "P", "E"];
86
- if (!signerData.typedoc || !validDocTypes.includes(signerData.typedoc)) {
136
+ if (
137
+ !validatedSignerData.typedoc ||
138
+ !validDocTypes.includes(validatedSignerData.typedoc)
139
+ ) {
87
140
  throw new ApacuanaAPIError(
88
141
  'El campo "typedoc" es requerido y debe ser uno de los siguientes valores: V, P, E.',
89
142
  400,
@@ -91,7 +144,7 @@ const validateOnBoardingSignerData = (signerData) => {
91
144
  );
92
145
  }
93
146
 
94
- if (!signerData.doc || typeof signerData.doc !== "string") {
147
+ if (!validatedSignerData.doc || typeof validatedSignerData.doc !== "string") {
95
148
  throw new ApacuanaAPIError(
96
149
  'El campo "doc" es requerido y debe ser una cadena de texto.',
97
150
  400,
@@ -100,8 +153,8 @@ const validateOnBoardingSignerData = (signerData) => {
100
153
  }
101
154
 
102
155
  if (
103
- !Array.isArray(signerData.signature) ||
104
- signerData.signature.length === 0
156
+ !Array.isArray(validatedSignerData.signature) ||
157
+ validatedSignerData.signature.length === 0
105
158
  ) {
106
159
  throw new ApacuanaAPIError(
107
160
  'El campo "signature" es requerido y debe ser un array no vacío.',
@@ -110,7 +163,7 @@ const validateOnBoardingSignerData = (signerData) => {
110
163
  );
111
164
  }
112
165
 
113
- signerData.signature.forEach((sig, index) => {
166
+ validatedSignerData.signature.forEach((sig, index) => {
114
167
  if (!Number.isInteger(sig.page) || sig.page < 1) {
115
168
  throw new ApacuanaAPIError(
116
169
  `La página de la firma ${
@@ -141,6 +194,19 @@ const validateOnBoardingSignerData = (signerData) => {
141
194
  );
142
195
  }
143
196
  });
197
+
198
+ const formData = new FormData();
199
+ Object.keys(validatedSignerData).forEach((key) => {
200
+ if (key === "signature") {
201
+ formData.append(key, JSON.stringify(validatedSignerData[key]));
202
+ } else if (key === "file") {
203
+ formData.append("document", validatedSignerData[key]);
204
+ } else if (validatedSignerData[key] !== undefined) {
205
+ formData.append(key, validatedSignerData[key]);
206
+ }
207
+ });
208
+
209
+ return formData;
144
210
  };
145
211
 
146
212
  const validateCsr = (encryptedCSR) => {
@@ -243,6 +309,26 @@ const validateOnBoardingSignDocumentData = (signData) => {
243
309
  "INVALID_PARAMETER"
244
310
  );
245
311
  }
312
+
313
+ const formData = new FormData();
314
+ formData.append(
315
+ "positions",
316
+ JSON.stringify(
317
+ signature.positions.map((p) => ({
318
+ x: p.x,
319
+ y: p.y,
320
+ page: p.page,
321
+ status: 1,
322
+ }))
323
+ )
324
+ );
325
+ formData.append("publickey", cert);
326
+ formData.append("signeddigest", signedDigest);
327
+ if (signData.document && signData.document instanceof File) {
328
+ formData.append("document", signData.document);
329
+ }
330
+
331
+ return formData;
246
332
  };
247
333
 
248
334
  export default {
@@ -255,4 +341,5 @@ export default {
255
341
  validateGetDocsData,
256
342
  validateGetDigestData,
257
343
  validateOnBoardingSignDocumentData,
344
+ createPayloadGetDigest,
258
345
  };
@@ -129,18 +129,23 @@ export const httpRequest = async (path, data = {}, method = "POST") => {
129
129
  let dataToSend;
130
130
  const requestConfig = {};
131
131
 
132
- const hasFile = Object.values(data).some(
133
- (value) => typeof File !== "undefined" && value instanceof File
134
- );
135
- if (hasFile) {
136
- const formData = new FormData();
137
- Object.keys(data).forEach((key) => {
138
- formData.append(key, data[key]);
139
- });
140
- dataToSend = formData;
132
+ if (typeof FormData !== "undefined" && data instanceof FormData) {
133
+ dataToSend = data;
141
134
  } else {
142
- dataToSend = { ...data };
135
+ const hasFile = Object.values(data).some(
136
+ (value) => typeof File !== "undefined" && value instanceof File
137
+ );
138
+ if (hasFile) {
139
+ const formData = new FormData();
140
+ Object.keys(data).forEach((key) => {
141
+ formData.append(key, data[key]);
142
+ });
143
+ dataToSend = formData;
144
+ } else {
145
+ dataToSend = { ...data };
146
+ }
143
147
  }
148
+
144
149
  try {
145
150
  let response;
146
151
  switch (method.toUpperCase()) {
@@ -1,8 +1,11 @@
1
- import * as faceLiveness from "../../src/api/faceLiveness";
1
+ import {
2
+ createFaceLivenessSession,
3
+ validateFaceLiveness,
4
+ } from "../../src/api/faceLiveness";
2
5
  import { httpRequest } from "../../src/utils/httpClient";
3
- import { getConfig } from "../../src/config";
4
- import { ApacuanaAPIError } from "../../src/errors";
5
6
  import ApacuanaSuccess from "../../src/success";
7
+ import { setConfig, getConfig } from "../../src/config";
8
+ import { ApacuanaAPIError } from "../../src/errors";
6
9
  import { INTEGRATION_TYPE } from "../../src/utils/constant";
7
10
 
8
11
  jest.mock("../../src/utils/httpClient", () => ({
@@ -11,6 +14,7 @@ jest.mock("../../src/utils/httpClient", () => ({
11
14
 
12
15
  jest.mock("../../src/config", () => ({
13
16
  getConfig: jest.fn(),
17
+ setConfig: jest.fn(),
14
18
  }));
15
19
 
16
20
  describe("FaceLiveness API", () => {
@@ -21,23 +25,25 @@ describe("FaceLiveness API", () => {
21
25
 
22
26
  describe("createFaceLivenessSession", () => {
23
27
  it("should create a session for ONBOARDING integration", async () => {
24
- httpRequest.mockResolvedValue({ sessionId: "test-session-id" });
28
+ const mockSessionId = "session-onboarding-123";
29
+ httpRequest.mockResolvedValue({ sessionid: mockSessionId });
30
+ setConfig({ onboardingId: "some-onboarding-id" });
25
31
 
26
- const result = await faceLiveness.createFaceLivenessSession();
32
+ const result = await createFaceLivenessSession();
27
33
 
28
- expect(result).toBeInstanceOf(ApacuanaSuccess);
29
- expect(result.data).toEqual({ sessionId: "test-session-id" });
30
34
  expect(httpRequest).toHaveBeenCalledWith(
31
35
  "services/api/faceliveness/create",
32
36
  {},
33
37
  "POST"
34
38
  );
39
+ expect(result).toBeInstanceOf(ApacuanaSuccess);
40
+ expect(result.data).toEqual({ sessionId: mockSessionId });
35
41
  });
36
42
 
37
- it("should throw an error if API response does not contain sessionId for ONBOARDING", async () => {
43
+ it("should throw an error if session ID is missing for ONBOARDING", async () => {
38
44
  httpRequest.mockResolvedValue({}); // No sessionId
39
45
 
40
- await expect(faceLiveness.createFaceLivenessSession()).rejects.toThrow(
46
+ await expect(createFaceLivenessSession()).rejects.toThrow(
41
47
  new ApacuanaAPIError(
42
48
  "The API response does not contain the session ID.",
43
49
  undefined,
@@ -51,7 +57,7 @@ describe("FaceLiveness API", () => {
51
57
  integrationType: INTEGRATION_TYPE.ONPREMISE,
52
58
  });
53
59
 
54
- await expect(faceLiveness.createFaceLivenessSession()).rejects.toThrow(
60
+ await expect(createFaceLivenessSession()).rejects.toThrow(
55
61
  new ApacuanaAPIError(
56
62
  "Creating a Face Liveness session is not supported for integration type: ONPREMISE",
57
63
  501,
@@ -63,7 +69,7 @@ describe("FaceLiveness API", () => {
63
69
  it("should throw an UNSUPPORTED_INTEGRATION_TYPE error for unsupported integration types", async () => {
64
70
  getConfig.mockReturnValue({ integrationType: "unsupported-type" });
65
71
 
66
- await expect(faceLiveness.createFaceLivenessSession()).rejects.toThrow(
72
+ await expect(createFaceLivenessSession()).rejects.toThrow(
67
73
  new ApacuanaAPIError(
68
74
  "Unsupported integration type: unsupported-type",
69
75
  400,
@@ -76,16 +82,14 @@ describe("FaceLiveness API", () => {
76
82
  const apiError = new ApacuanaAPIError("API Error", 500, "API_ERROR");
77
83
  httpRequest.mockRejectedValue(apiError);
78
84
 
79
- await expect(faceLiveness.createFaceLivenessSession()).rejects.toThrow(
80
- apiError
81
- );
85
+ await expect(createFaceLivenessSession()).rejects.toThrow(apiError);
82
86
  });
83
87
 
84
88
  it("should throw a generic error for other failures during session creation", async () => {
85
89
  const genericError = new Error("Network Error");
86
90
  httpRequest.mockRejectedValue(genericError);
87
91
 
88
- await expect(faceLiveness.createFaceLivenessSession()).rejects.toThrow(
92
+ await expect(createFaceLivenessSession()).rejects.toThrow(
89
93
  `Failed to create Face Liveness session: ${genericError.message}`
90
94
  );
91
95
  });
@@ -95,7 +99,7 @@ describe("FaceLiveness API", () => {
95
99
  const sessionId = "test-session-id";
96
100
 
97
101
  it("should throw an error if sessionId is not provided", async () => {
98
- await expect(faceLiveness.validateFaceLiveness({})).rejects.toThrow(
102
+ await expect(validateFaceLiveness({})).rejects.toThrow(
99
103
  new ApacuanaAPIError(
100
104
  "sessionId is a required parameter.",
101
105
  400,
@@ -107,7 +111,7 @@ describe("FaceLiveness API", () => {
107
111
  it("should return 'verified' on status 200", async () => {
108
112
  httpRequest.mockResolvedValue({ someData: "data" });
109
113
 
110
- const result = await faceLiveness.validateFaceLiveness({ sessionId });
114
+ const result = await validateFaceLiveness({ sessionId });
111
115
 
112
116
  expect(result).toBeInstanceOf(ApacuanaSuccess);
113
117
  expect(result.data).toEqual({ status: "verified", someData: "data" });
@@ -126,14 +130,10 @@ describe("FaceLiveness API", () => {
126
130
  ])(
127
131
  "should return status '%s' for statusCode %i",
128
132
  async (statusCode, status) => {
129
- const apiError = new ApacuanaAPIError(
130
- "Error",
131
- statusCode,
132
- "API_ERROR"
133
- );
133
+ const apiError = new ApacuanaAPIError("Error", statusCode, "API_ERROR");
134
134
  httpRequest.mockRejectedValue(apiError);
135
135
 
136
- const result = await faceLiveness.validateFaceLiveness({ sessionId });
136
+ const result = await validateFaceLiveness({ sessionId });
137
137
 
138
138
  expect(result).toBeInstanceOf(ApacuanaSuccess);
139
139
  expect(result.data).toEqual({ status });
@@ -148,9 +148,9 @@ describe("FaceLiveness API", () => {
148
148
  );
149
149
  httpRequest.mockRejectedValue(apiError);
150
150
 
151
- await expect(
152
- faceLiveness.validateFaceLiveness({ sessionId })
153
- ).rejects.toThrow(apiError);
151
+ await expect(validateFaceLiveness({ sessionId })).rejects.toThrow(
152
+ apiError
153
+ );
154
154
  });
155
155
 
156
156
  it("should throw a NOT_IMPLEMENTED error for ONPREMISE integration", async () => {
@@ -158,9 +158,7 @@ describe("FaceLiveness API", () => {
158
158
  integrationType: INTEGRATION_TYPE.ONPREMISE,
159
159
  });
160
160
 
161
- await expect(
162
- faceLiveness.validateFaceLiveness({ sessionId })
163
- ).rejects.toThrow(
161
+ await expect(validateFaceLiveness({ sessionId })).rejects.toThrow(
164
162
  new ApacuanaAPIError(
165
163
  "Validating a Face Liveness session is not supported for integration type: ONPREMISE",
166
164
  501,
@@ -169,4 +167,4 @@ describe("FaceLiveness API", () => {
169
167
  );
170
168
  });
171
169
  });
172
- });
170
+ });
@@ -35,7 +35,7 @@ describe("Revocations API", () => {
35
35
  const result = await requestRevocation(params);
36
36
 
37
37
  expect(httpRequest).toHaveBeenCalledWith(
38
- "services/api/certificate/revocation",
38
+ "services/api/onboardingclient/requestcert",
39
39
  params,
40
40
  "POST"
41
41
  );
@@ -64,18 +64,18 @@ describe("Revocations API", () => {
64
64
 
65
65
  describe("getRevocationReasons", () => {
66
66
  it("should make a GET request and return the reasons for ONBOARDING", async () => {
67
- const mockReasons = [{ code: 1, reason: "Test Reason" }];
67
+ const mockReasons = [{ code: "01", description: "Test Reason" }];
68
68
  httpRequest.mockResolvedValue({ records: mockReasons });
69
69
 
70
70
  const result = await getRevocationReasons();
71
71
 
72
72
  expect(httpRequest).toHaveBeenCalledWith(
73
- "services/api/certificate/revocation-reasons",
73
+ "config/api/revocation/reasonsonboarding",
74
74
  {},
75
75
  "GET"
76
76
  );
77
77
  expect(result).toBeInstanceOf(ApacuanaSuccess);
78
- expect(result.data).toEqual(mockReasons);
78
+ expect(result.data).toEqual({ reasons: mockReasons });
79
79
  });
80
80
 
81
81
  it("should throw an error if the API response does not contain 'records'", async () => {
@@ -24,9 +24,32 @@ jest.mock("../../src/utils/helpers", () => ({
24
24
  validateGetDocsData: jest.fn(),
25
25
  validateGetDigestData: jest.fn(),
26
26
  validateOnBoardingSignDocumentData: jest.fn(),
27
+ createPayloadGetDigest: jest.fn(),
27
28
  }));
28
29
 
29
30
  describe("API - Signatures", () => {
31
+ beforeAll(() => {
32
+ // FormData no existe en el entorno de Node.js de Jest.
33
+ // Creamos una simulación (mock) que incluye los métodos 'append' y 'forEach'.
34
+ global.FormData = class FormData {
35
+ constructor() {
36
+ this.data = new Map();
37
+ }
38
+ append(key, value) {
39
+ this.data.set(key, value);
40
+ }
41
+ forEach(callback) {
42
+ // Delegamos la llamada forEach al Map interno.
43
+ this.data.forEach((value, key) => callback(value, key));
44
+ }
45
+ };
46
+ });
47
+
48
+ afterAll(() => {
49
+ // Limpiamos la simulación global después de las pruebas.
50
+ delete global.FormData;
51
+ });
52
+
30
53
  afterEach(() => {
31
54
  jest.resetAllMocks();
32
55
  });
@@ -37,6 +60,12 @@ describe("API - Signatures", () => {
37
60
  integrationType: INTEGRATION_TYPE.ONBOARDING,
38
61
  });
39
62
  const signerData = { typedoc: "V", doc: "12345678" };
63
+
64
+ const formData = new FormData();
65
+ formData.append("typedoc", "V");
66
+ formData.append("doc", "12345678");
67
+
68
+ helpers.validateOnBoardingSignerData.mockReturnValue(formData);
40
69
  httpRequest.mockResolvedValue({ success: true });
41
70
 
42
71
  await addSigner(signerData);
@@ -45,8 +74,8 @@ describe("API - Signatures", () => {
45
74
  signerData
46
75
  );
47
76
  expect(httpRequest).toHaveBeenCalledWith(
48
- "services/api/documents/signing",
49
- signerData,
77
+ "services/api/documents/signingsdk",
78
+ formData,
50
79
  "POST"
51
80
  );
52
81
  });
@@ -177,12 +206,17 @@ describe("API - Signatures", () => {
177
206
  const mockResponse = { data: { digest: "test-digest" } };
178
207
  httpRequest.mockResolvedValue(mockResponse);
179
208
 
209
+ const expectedFormData = new FormData();
210
+ expectedFormData.append("publickey", "test-cert");
211
+ helpers.createPayloadGetDigest.mockReturnValue(expectedFormData);
212
+
180
213
  const result = await getDigest(signData);
181
214
 
182
215
  expect(helpers.validateGetDigestData).toHaveBeenCalledWith(signData);
216
+ expect(helpers.createPayloadGetDigest).toHaveBeenCalledWith(signData);
183
217
  expect(httpRequest).toHaveBeenCalledWith(
184
218
  `services/api/documents/getdigest/${signData.signatureId}`,
185
- { publickey: signData.cert },
219
+ expectedFormData,
186
220
  "POST"
187
221
  );
188
222
  expect(result).toBeInstanceOf(ApacuanaSuccess);
@@ -243,20 +277,33 @@ describe("API - Signatures", () => {
243
277
  const mockResponse = { success: true };
244
278
  httpRequest.mockResolvedValue(mockResponse);
245
279
 
280
+ const expectedFormData = new FormData();
281
+ expectedFormData.append(
282
+ "positions",
283
+ JSON.stringify(
284
+ signData.signature.positions.map((p) => ({
285
+ x: p.x,
286
+ y: p.y,
287
+ page: p.page,
288
+ status: 1,
289
+ }))
290
+ )
291
+ );
292
+ expectedFormData.append("publickey", signData.cert);
293
+ expectedFormData.append("signeddigest", signData.signedDigest);
294
+
295
+ helpers.validateOnBoardingSignDocumentData.mockReturnValue(
296
+ expectedFormData
297
+ );
298
+
246
299
  const result = await signDocument(signData);
247
300
 
248
- expect(
249
- helpers.validateOnBoardingSignDocumentData
250
- ).toHaveBeenCalledWith(signData);
301
+ expect(helpers.validateOnBoardingSignDocumentData).toHaveBeenCalledWith(
302
+ signData
303
+ );
251
304
  expect(httpRequest).toHaveBeenCalledWith(
252
305
  `services/api/documents/sign/${signData.signature.id}`,
253
- {
254
- positions: JSON.stringify([
255
- { x: 0.1, y: 0.2, page: 1, status: 1 },
256
- ]),
257
- publickey: signData.cert,
258
- signeddigest: signData.signedDigest,
259
- },
306
+ expectedFormData,
260
307
  "PUT"
261
308
  );
262
309
  expect(result).toBeInstanceOf(ApacuanaSuccess);
@@ -306,10 +353,18 @@ describe("API - Signatures", () => {
306
353
 
307
354
  it("should throw an error if data or data.file is not provided", async () => {
308
355
  await expect(uploadSignatureVariant(null)).rejects.toThrow(
309
- new ApacuanaAPIError("El parámetro 'file' es requerido.", 400, "INVALID_PARAMETER")
356
+ new ApacuanaAPIError(
357
+ "El parámetro 'file' es requerido.",
358
+ 400,
359
+ "INVALID_PARAMETER"
360
+ )
310
361
  );
311
362
  await expect(uploadSignatureVariant({})).rejects.toThrow(
312
- new ApacuanaAPIError("El parámetro 'file' es requerido.", 400, "INVALID_PARAMETER")
363
+ new ApacuanaAPIError(
364
+ "El parámetro 'file' es requerido.",
365
+ 400,
366
+ "INVALID_PARAMETER"
367
+ )
313
368
  );
314
369
  });
315
370
 
@@ -325,7 +380,9 @@ describe("API - Signatures", () => {
325
380
 
326
381
  it("should throw an error if file is not a PNG", async () => {
327
382
  const invalidFile = new File([], "test.txt", { type: "text/plain" });
328
- await expect(uploadSignatureVariant({ file: invalidFile })).rejects.toThrow(
383
+ await expect(
384
+ uploadSignatureVariant({ file: invalidFile })
385
+ ).rejects.toThrow(
329
386
  new ApacuanaAPIError(
330
387
  "El archivo debe ser de tipo PNG (image/png).",
331
388
  400,