apacuana-sdk-core 1.26.0 → 1.26.2

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 (46) hide show
  1. package/.env +8 -0
  2. package/README.md +69 -0
  3. package/coverage/clover.xml +192 -147
  4. package/coverage/coverage-final.json +4 -4
  5. package/coverage/lcov-report/index.html +27 -27
  6. package/coverage/lcov-report/src/api/certs.js.html +1 -1
  7. package/coverage/lcov-report/src/api/faceLiveness.js.html +1 -1
  8. package/coverage/lcov-report/src/api/index.html +18 -18
  9. package/coverage/lcov-report/src/api/revocations.js.html +1 -1
  10. package/coverage/lcov-report/src/api/signatures.js.html +413 -11
  11. package/coverage/lcov-report/src/api/users.js.html +1 -1
  12. package/coverage/lcov-report/src/config/index.html +1 -1
  13. package/coverage/lcov-report/src/config/index.js.html +1 -1
  14. package/coverage/lcov-report/src/errors/index.html +1 -1
  15. package/coverage/lcov-report/src/errors/index.js.html +8 -8
  16. package/coverage/lcov-report/src/index.html +15 -15
  17. package/coverage/lcov-report/src/index.js.html +41 -8
  18. package/coverage/lcov-report/src/success/index.html +1 -1
  19. package/coverage/lcov-report/src/success/index.js.html +5 -5
  20. package/coverage/lcov-report/src/utils/constant.js.html +1 -1
  21. package/coverage/lcov-report/src/utils/helpers.js.html +1 -1
  22. package/coverage/lcov-report/src/utils/httpClient.js.html +1 -1
  23. package/coverage/lcov-report/src/utils/index.html +1 -1
  24. package/coverage/lcov.info +343 -253
  25. package/dist/api/signatures.d.ts +5 -0
  26. package/dist/index.d.ts +2 -0
  27. package/dist/index.js +290 -93
  28. package/dist/index.js.map +1 -1
  29. package/dist/index.mjs +290 -93
  30. package/dist/index.mjs.map +1 -1
  31. package/package.json +1 -1
  32. package/src/api/signatures.js +136 -2
  33. package/src/index.js +11 -0
  34. package/tests/api/signatures.test.js +166 -2
  35. package/tests/integration/createAndLockDocument.test.js +93 -0
  36. package/coverage/lcov-report/api/index.html +0 -131
  37. package/coverage/lcov-report/api/signatures.js.html +0 -1093
  38. package/coverage/lcov-report/api/users.js.html +0 -292
  39. package/coverage/lcov-report/certs.js.html +0 -175
  40. package/coverage/lcov-report/config/index.html +0 -116
  41. package/coverage/lcov-report/config/index.js.html +0 -208
  42. package/coverage/lcov-report/errors/index.html +0 -116
  43. package/coverage/lcov-report/errors/index.js.html +0 -148
  44. package/coverage/lcov-report/utils/constant.js.html +0 -145
  45. package/coverage/lcov-report/utils/httpClient.js.html +0 -646
  46. package/coverage/lcov-report/utils/index.html +0 -131
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "apacuana-sdk-core",
3
- "version": "1.26.0",
3
+ "version": "1.26.2",
4
4
  "description": "Core SDK para interacciones con las APIs de Apacuana.",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -240,12 +240,10 @@ const deleteSignatureVariantOnPremise = async () => {
240
240
 
241
241
  const addMemberOnBoarding = async (memberData) => {
242
242
  const payload = helpers.validateAddMemberData(memberData);
243
- // Transform documentId to docid for API compatibility
244
243
  const apiPayload = {
245
244
  docid: payload.documentId,
246
245
  kinddoc: payload.typedoc,
247
246
  doc: payload.doc,
248
- signature: payload.signature,
249
247
  };
250
248
  try {
251
249
  const response = await httpRequest(
@@ -272,6 +270,64 @@ const addMemberOnPremise = async () => {
272
270
  );
273
271
  };
274
272
 
273
+ const createDocumentOnBoarding = async (documentData) => {
274
+ const formData = new FormData();
275
+ formData.append("name", documentData.name);
276
+ formData.append("reference", documentData.reference);
277
+ try {
278
+ const response = await httpRequest(
279
+ "services/api/documents",
280
+ formData,
281
+ "POST"
282
+ );
283
+ return new ApacuanaSuccess({
284
+ msg: response.msg,
285
+ id: response.id,
286
+ });
287
+ } catch (error) {
288
+ if (error instanceof ApacuanaAPIError) {
289
+ throw error;
290
+ }
291
+ throw new ApacuanaAPIError(
292
+ `Failed to create document: ${error.message || "Unknown error"}`
293
+ );
294
+ }
295
+ };
296
+
297
+ const createDocumentOnPremise = async () => {
298
+ throw new ApacuanaAPIError(
299
+ "Creating documents is not supported for integration type: ONPREMISE",
300
+ 501,
301
+ "NOT_IMPLEMENTED"
302
+ );
303
+ };
304
+
305
+ const lockDocumentOnBoarding = async (documentId) => {
306
+ try {
307
+ const response = await httpRequest(
308
+ `services/api/documents/lock/${documentId}`,
309
+ {},
310
+ "PUT"
311
+ );
312
+ return new ApacuanaSuccess(response);
313
+ } catch (error) {
314
+ if (error instanceof ApacuanaAPIError) {
315
+ throw error;
316
+ }
317
+ throw new ApacuanaAPIError(
318
+ `Failed to lock document: ${error.message || "Unknown error"}`
319
+ );
320
+ }
321
+ };
322
+
323
+ const lockDocumentOnPremise = async () => {
324
+ throw new ApacuanaAPIError(
325
+ "Locking documents is not supported for integration type: ONPREMISE",
326
+ 501,
327
+ "NOT_IMPLEMENTED"
328
+ );
329
+ };
330
+
275
331
  // =================================================================
276
332
  // Exported Functions
277
333
  // =================================================================
@@ -506,3 +562,81 @@ export const addMember = async (memberData) => {
506
562
  "UNSUPPORTED_INTEGRATION_TYPE"
507
563
  );
508
564
  };
565
+
566
+ /**
567
+ * Crea un nuevo documento para ser firmado.
568
+ * @param {object} documentData - Datos del documento a crear.
569
+ * @param {string} documentData.name - Nombre del documento.
570
+ * @param {string} documentData.reference - Referencia del documento.
571
+ * @returns {Promise<object>} Una promesa que resuelve a un objeto con `{ msg, id }` del documento creado.
572
+ * @throws {ApacuanaAPIError} Si los datos son inválidos, la llamada a la API falla, o el tipo de integración no es soportado.
573
+ */
574
+ export const createDocument = async (documentData) => {
575
+ if (
576
+ !documentData ||
577
+ typeof documentData !== "object" ||
578
+ Object.keys(documentData).length === 0
579
+ ) {
580
+ throw new ApacuanaAPIError(
581
+ "Document data (documentData) is required and must be a non-empty object.",
582
+ 400,
583
+ "INVALID_PARAMETER"
584
+ );
585
+ }
586
+
587
+ if (!documentData.name || !documentData.reference) {
588
+ throw new ApacuanaAPIError(
589
+ "Fields 'name' and 'reference' are required.",
590
+ 400,
591
+ "INVALID_PARAMETER"
592
+ );
593
+ }
594
+
595
+ const { integrationType } = getConfig();
596
+
597
+ if (integrationType === INTEGRATION_TYPE.ONBOARDING) {
598
+ return createDocumentOnBoarding(documentData);
599
+ }
600
+
601
+ if (integrationType === INTEGRATION_TYPE.ONPREMISE) {
602
+ return createDocumentOnPremise();
603
+ }
604
+
605
+ throw new ApacuanaAPIError(
606
+ `Unsupported integration type: ${integrationType}`,
607
+ 400,
608
+ "UNSUPPORTED_INTEGRATION_TYPE"
609
+ );
610
+ };
611
+
612
+ /**
613
+ * Bloquea un documento para evitar modificaciones.
614
+ * @param {string} documentId - ID (UUID) del documento a bloquear.
615
+ * @returns {Promise<object>} Una promesa que resuelve al response de la API.
616
+ * @throws {ApacuanaAPIError} Si el documentId es inválido, la llamada a la API falla, o el tipo de integración no es soportado.
617
+ */
618
+ export const lockDocument = async (documentId) => {
619
+ if (!documentId || typeof documentId !== "string") {
620
+ throw new ApacuanaAPIError(
621
+ "Field 'documentId' is required and must be a string.",
622
+ 400,
623
+ "INVALID_PARAMETER"
624
+ );
625
+ }
626
+
627
+ const { integrationType } = getConfig();
628
+
629
+ if (integrationType === INTEGRATION_TYPE.ONBOARDING) {
630
+ return lockDocumentOnBoarding(documentId);
631
+ }
632
+
633
+ if (integrationType === INTEGRATION_TYPE.ONPREMISE) {
634
+ return lockDocumentOnPremise();
635
+ }
636
+
637
+ throw new ApacuanaAPIError(
638
+ `Unsupported integration type: ${integrationType}`,
639
+ 400,
640
+ "UNSUPPORTED_INTEGRATION_TYPE"
641
+ );
642
+ };
package/src/index.js CHANGED
@@ -10,10 +10,12 @@ import {
10
10
  import {
11
11
  addSigner,
12
12
  addMember,
13
+ createDocument,
13
14
  deleteSignatureVariant,
14
15
  getDigest,
15
16
  getDocs,
16
17
  getSignatureVariant,
18
+ lockDocument,
17
19
  signDocument,
18
20
  uploadSignatureVariant,
19
21
  } from "./api/signatures";
@@ -161,6 +163,15 @@ const apacuana = {
161
163
  return sendFaceLiveness(data);
162
164
  },
163
165
 
166
+ createDocument: (data) => {
167
+ checkSdk(false);
168
+ return createDocument(data);
169
+ },
170
+ lockDocument: (documentId) => {
171
+ checkSdk(false);
172
+ return lockDocument(documentId);
173
+ },
174
+
164
175
  close: () => close(),
165
176
  getConfig,
166
177
  };
@@ -5,12 +5,14 @@ import ApacuanaSuccess from "../../src/success";
5
5
  import {
6
6
  addSigner,
7
7
  addMember,
8
+ createDocument,
9
+ deleteSignatureVariant,
8
10
  getDocs,
9
11
  getDigest,
12
+ getSignatureVariant,
13
+ lockDocument,
10
14
  signDocument,
11
15
  uploadSignatureVariant,
12
- getSignatureVariant,
13
- deleteSignatureVariant,
14
16
  } from "../../src/api/signatures";
15
17
  import helpers from "../../src/utils/helpers";
16
18
  import { INTEGRATION_TYPE } from "../../src/utils/constant";
@@ -622,4 +624,166 @@ describe("API - Signatures", () => {
622
624
  );
623
625
  });
624
626
  });
627
+
628
+ describe("createDocument", () => {
629
+ it("should create a document for ONBOARDING integration", async () => {
630
+ getConfig.mockReturnValue({
631
+ integrationType: INTEGRATION_TYPE.ONBOARDING,
632
+ });
633
+ const documentData = { name: "prueba", reference: "prueba" };
634
+ const mockResponse = {
635
+ msg: "Documento creado",
636
+ id: "ef3344bb-d114-46b5-8356-90ec113cbbbd",
637
+ };
638
+ httpRequest.mockResolvedValue(mockResponse);
639
+
640
+ const result = await createDocument(documentData);
641
+
642
+ expect(httpRequest).toHaveBeenCalledWith(
643
+ "services/api/documents",
644
+ expect.any(FormData),
645
+ "POST"
646
+ );
647
+ expect(result).toBeInstanceOf(ApacuanaSuccess);
648
+ expect(result.data).toEqual(mockResponse);
649
+ });
650
+
651
+ it("should throw not implemented for ONPREMISE integration", async () => {
652
+ getConfig.mockReturnValue({
653
+ integrationType: INTEGRATION_TYPE.ONPREMISE,
654
+ });
655
+
656
+ await expect(
657
+ createDocument({ name: "x", reference: "y" })
658
+ ).rejects.toThrow(
659
+ new ApacuanaAPIError(
660
+ "Creating documents is not supported for integration type: ONPREMISE",
661
+ 501,
662
+ "NOT_IMPLEMENTED"
663
+ )
664
+ );
665
+ });
666
+
667
+ it("should throw an error for unsupported integration type", async () => {
668
+ getConfig.mockReturnValue({ integrationType: "UNSUPPORTED" });
669
+ await expect(
670
+ createDocument({ name: "x", reference: "y" })
671
+ ).rejects.toThrow(
672
+ new ApacuanaAPIError(
673
+ "Unsupported integration type: UNSUPPORTED",
674
+ 400,
675
+ "UNSUPPORTED_INTEGRATION_TYPE"
676
+ )
677
+ );
678
+ });
679
+
680
+ it("should throw an error if documentData is invalid", async () => {
681
+ await expect(createDocument(null)).rejects.toThrow(
682
+ new ApacuanaAPIError(
683
+ "Document data (documentData) is required and must be a non-empty object.",
684
+ 400,
685
+ "INVALID_PARAMETER"
686
+ )
687
+ );
688
+ });
689
+
690
+ it("should throw an error if name or reference is missing", async () => {
691
+ await expect(createDocument({ name: "x" })).rejects.toThrow(
692
+ new ApacuanaAPIError(
693
+ "Fields 'name' and 'reference' are required.",
694
+ 400,
695
+ "INVALID_PARAMETER"
696
+ )
697
+ );
698
+ });
699
+
700
+ it("should wrap non-ApacuanaAPIError from httpRequest as ApacuanaAPIError", async () => {
701
+ getConfig.mockReturnValue({
702
+ integrationType: INTEGRATION_TYPE.ONBOARDING,
703
+ });
704
+ httpRequest.mockRejectedValue(new Error("Network error"));
705
+
706
+ await expect(
707
+ createDocument({ name: "x", reference: "y" })
708
+ ).rejects.toThrow(
709
+ new ApacuanaAPIError("Failed to create document: Network error")
710
+ );
711
+ });
712
+ });
713
+
714
+ describe("lockDocument", () => {
715
+ it("should lock a document for ONBOARDING integration", async () => {
716
+ getConfig.mockReturnValue({
717
+ integrationType: INTEGRATION_TYPE.ONBOARDING,
718
+ });
719
+ const documentId = "ccb318a8-e8ff-4a17-ba20-10acbcd96785";
720
+ const mockResponse = { success: true, locked: true };
721
+ httpRequest.mockResolvedValue(mockResponse);
722
+
723
+ const result = await lockDocument(documentId);
724
+
725
+ expect(httpRequest).toHaveBeenCalledWith(
726
+ `services/api/documents/lock/${documentId}`,
727
+ {},
728
+ "PUT"
729
+ );
730
+ expect(result).toBeInstanceOf(ApacuanaSuccess);
731
+ expect(result.data).toEqual(mockResponse);
732
+ });
733
+
734
+ it("should throw not implemented for ONPREMISE integration", async () => {
735
+ getConfig.mockReturnValue({
736
+ integrationType: INTEGRATION_TYPE.ONPREMISE,
737
+ });
738
+
739
+ await expect(lockDocument("ccb318a8-e8ff-4a17-ba20-10acbcd96785")).rejects.toThrow(
740
+ new ApacuanaAPIError(
741
+ "Locking documents is not supported for integration type: ONPREMISE",
742
+ 501,
743
+ "NOT_IMPLEMENTED"
744
+ )
745
+ );
746
+ });
747
+
748
+ it("should throw an error for unsupported integration type", async () => {
749
+ getConfig.mockReturnValue({ integrationType: "UNSUPPORTED" });
750
+ await expect(lockDocument("ccb318a8-e8ff-4a17-ba20-10acbcd96785")).rejects.toThrow(
751
+ new ApacuanaAPIError(
752
+ "Unsupported integration type: UNSUPPORTED",
753
+ 400,
754
+ "UNSUPPORTED_INTEGRATION_TYPE"
755
+ )
756
+ );
757
+ });
758
+
759
+ it("should throw an error if documentId is missing or not a string", async () => {
760
+ await expect(lockDocument(null)).rejects.toThrow(
761
+ new ApacuanaAPIError(
762
+ "Field 'documentId' is required and must be a string.",
763
+ 400,
764
+ "INVALID_PARAMETER"
765
+ )
766
+ );
767
+ await expect(lockDocument(123)).rejects.toThrow(
768
+ new ApacuanaAPIError(
769
+ "Field 'documentId' is required and must be a string.",
770
+ 400,
771
+ "INVALID_PARAMETER"
772
+ )
773
+ );
774
+ });
775
+
776
+ it("should wrap non-ApacuanaAPIError from httpRequest as ApacuanaAPIError", async () => {
777
+ getConfig.mockReturnValue({
778
+ integrationType: INTEGRATION_TYPE.ONBOARDING,
779
+ });
780
+ httpRequest.mockRejectedValue(new Error("Network error"));
781
+
782
+ await expect(
783
+ lockDocument("ccb318a8-e8ff-4a17-ba20-10acbcd96785")
784
+ ).rejects.toThrow(
785
+ new ApacuanaAPIError("Failed to lock document: Network error")
786
+ );
787
+ });
788
+ });
625
789
  });
@@ -0,0 +1,93 @@
1
+ /**
2
+ * Integration tests for createDocument → lockDocument flow
3
+ *
4
+ * Requires environment variables (copy .env.example to .env and fill in):
5
+ * APACUANA_API_URL=https://api.dev.apacuana.3dlinkweb.com
6
+ * APACUANA_SECRET_KEY=your-secret-key
7
+ * APACUANA_API_KEY=your-api-key
8
+ * APACUANA_VERIFICATION_ID=your-verification-id
9
+ * APACUANA_INTEGRATION_TYPE=ONBOARDING
10
+ *
11
+ * Note: APACUANA_DOCUMENT_REFERENCE is provided by the backend team for this
12
+ * sandbox environment; it's a reference id the document will be linked to.
13
+ *
14
+ * Run with: NODE_ENV=test npm test -- --testPathPatterns="integration/createAndLockDocument.test.js"
15
+ */
16
+ import { readFileSync } from "fs";
17
+ import { resolve } from "path";
18
+
19
+ const envPath = resolve(process.cwd(), ".env");
20
+
21
+ try {
22
+ const envContent = readFileSync(envPath, "utf-8");
23
+ envContent.split("\n").forEach(line => {
24
+ const [key, ...valueParts] = line.split("=");
25
+ if (key && key.trim() && !key.startsWith("#")) {
26
+ process.env[key.trim()] = valueParts.join("=").replace(/"/g, "").trim();
27
+ }
28
+ });
29
+ } catch (err) {
30
+ // .env file not found, continue without it
31
+ }
32
+
33
+ import apacuana from "../../src/index.js";
34
+
35
+ const requiredEnvVars = [
36
+ "APACUANA_API_URL",
37
+ "APACUANA_SECRET_KEY",
38
+ "APACUANA_API_KEY",
39
+ "APACUANA_VERIFICATION_ID",
40
+ "APACUANA_INTEGRATION_TYPE",
41
+ "APACUANA_DOCUMENT_REFERENCE",
42
+ ];
43
+
44
+ const missingVars = requiredEnvVars.filter(v => !process.env[v]);
45
+ if (missingVars.length > 0) {
46
+ test.skip("createDocument then lockDocument integration", () => {});
47
+ throw new Error(
48
+ `\n⚠️ Missing environment variables: ${missingVars.join(", ")}\n` +
49
+ "Create a .env file based on .env.example.\n"
50
+ );
51
+ }
52
+
53
+ describe("createDocument → lockDocument - Integration", () => {
54
+ beforeAll(async () => {
55
+ await apacuana.init({
56
+ apiUrl: process.env.APACUANA_API_URL,
57
+ secretKey: process.env.APACUANA_SECRET_KEY,
58
+ apiKey: process.env.APACUANA_API_KEY,
59
+ verificationId: process.env.APACUANA_VERIFICATION_ID,
60
+ integrationType: process.env.APACUANA_INTEGRATION_TYPE,
61
+ // customerId omitted: init() would set Authorization: Bearer which the
62
+ // backend rejects on this endpoint with 404 "Cliente no encontrado".
63
+ });
64
+ });
65
+
66
+ afterAll(() => {
67
+ apacuana.close();
68
+ });
69
+
70
+ test("should create a document and then lock it using the returned id", async () => {
71
+ const timestamp = Date.now();
72
+ const documentData = {
73
+ name: `integration-test-${timestamp}`,
74
+ reference: process.env.APACUANA_DOCUMENT_REFERENCE,
75
+ };
76
+
77
+ const createResult = await apacuana.createDocument(documentData);
78
+
79
+ expect(createResult).toBeDefined();
80
+ expect(createResult.success).toBe(true);
81
+ expect(createResult.data).toBeDefined();
82
+ expect(createResult.data.id).toBeDefined();
83
+ expect(typeof createResult.data.id).toBe("string");
84
+
85
+ const documentId = createResult.data.id;
86
+
87
+ const lockResult = await apacuana.lockDocument(documentId);
88
+
89
+ expect(lockResult).toBeDefined();
90
+ expect(lockResult.success).toBe(true);
91
+ expect(lockResult.data).toBeDefined();
92
+ });
93
+ });
@@ -1,131 +0,0 @@
1
-
2
- <!doctype html>
3
- <html lang="en">
4
-
5
- <head>
6
- <title>Code coverage report for api</title>
7
- <meta charset="utf-8" />
8
- <link rel="stylesheet" href="../prettify.css" />
9
- <link rel="stylesheet" href="../base.css" />
10
- <link rel="shortcut icon" type="image/x-icon" href="../favicon.png" />
11
- <meta name="viewport" content="width=device-width, initial-scale=1" />
12
- <style type='text/css'>
13
- .coverage-summary .sorter {
14
- background-image: url(../sort-arrow-sprite.png);
15
- }
16
- </style>
17
- </head>
18
-
19
- <body>
20
- <div class='wrapper'>
21
- <div class='pad1'>
22
- <h1><a href="../index.html">All files</a> api</h1>
23
- <div class='clearfix'>
24
-
25
- <div class='fl pad1y space-right2'>
26
- <span class="strong">57.29% </span>
27
- <span class="quiet">Statements</span>
28
- <span class='fraction'>55/96</span>
29
- </div>
30
-
31
-
32
- <div class='fl pad1y space-right2'>
33
- <span class="strong">50.98% </span>
34
- <span class="quiet">Branches</span>
35
- <span class='fraction'>26/51</span>
36
- </div>
37
-
38
-
39
- <div class='fl pad1y space-right2'>
40
- <span class="strong">50% </span>
41
- <span class="quiet">Functions</span>
42
- <span class='fraction'>7/14</span>
43
- </div>
44
-
45
-
46
- <div class='fl pad1y space-right2'>
47
- <span class="strong">57.29% </span>
48
- <span class="quiet">Lines</span>
49
- <span class='fraction'>55/96</span>
50
- </div>
51
-
52
-
53
- </div>
54
- <p class="quiet">
55
- Press <em>n</em> or <em>j</em> to go to the next uncovered block, <em>b</em>, <em>p</em> or <em>k</em> for the previous block.
56
- </p>
57
- <template id="filterTemplate">
58
- <div class="quiet">
59
- Filter:
60
- <input type="search" id="fileSearch">
61
- </div>
62
- </template>
63
- </div>
64
- <div class='status-line medium'></div>
65
- <div class="pad1">
66
- <table class="coverage-summary">
67
- <thead>
68
- <tr>
69
- <th data-col="file" data-fmt="html" data-html="true" class="file">File</th>
70
- <th data-col="pic" data-type="number" data-fmt="html" data-html="true" class="pic"></th>
71
- <th data-col="statements" data-type="number" data-fmt="pct" class="pct">Statements</th>
72
- <th data-col="statements_raw" data-type="number" data-fmt="html" class="abs"></th>
73
- <th data-col="branches" data-type="number" data-fmt="pct" class="pct">Branches</th>
74
- <th data-col="branches_raw" data-type="number" data-fmt="html" class="abs"></th>
75
- <th data-col="functions" data-type="number" data-fmt="pct" class="pct">Functions</th>
76
- <th data-col="functions_raw" data-type="number" data-fmt="html" class="abs"></th>
77
- <th data-col="lines" data-type="number" data-fmt="pct" class="pct">Lines</th>
78
- <th data-col="lines_raw" data-type="number" data-fmt="html" class="abs"></th>
79
- </tr>
80
- </thead>
81
- <tbody><tr>
82
- <td class="file medium" data-value="signatures.js"><a href="signatures.js.html">signatures.js</a></td>
83
- <td data-value="50.6" class="pic medium">
84
- <div class="chart"><div class="cover-fill" style="width: 50%"></div><div class="cover-empty" style="width: 50%"></div></div>
85
- </td>
86
- <td data-value="50.6" class="pct medium">50.6%</td>
87
- <td data-value="83" class="abs medium">42/83</td>
88
- <td data-value="38.46" class="pct low">38.46%</td>
89
- <td data-value="39" class="abs low">15/39</td>
90
- <td data-value="46.15" class="pct low">46.15%</td>
91
- <td data-value="13" class="abs low">6/13</td>
92
- <td data-value="50.6" class="pct medium">50.6%</td>
93
- <td data-value="83" class="abs medium">42/83</td>
94
- </tr>
95
-
96
- <tr>
97
- <td class="file high" data-value="users.js"><a href="users.js.html">users.js</a></td>
98
- <td data-value="100" class="pic high">
99
- <div class="chart"><div class="cover-fill cover-full" style="width: 100%"></div><div class="cover-empty" style="width: 0%"></div></div>
100
- </td>
101
- <td data-value="100" class="pct high">100%</td>
102
- <td data-value="13" class="abs high">13/13</td>
103
- <td data-value="91.66" class="pct high">91.66%</td>
104
- <td data-value="12" class="abs high">11/12</td>
105
- <td data-value="100" class="pct high">100%</td>
106
- <td data-value="1" class="abs high">1/1</td>
107
- <td data-value="100" class="pct high">100%</td>
108
- <td data-value="13" class="abs high">13/13</td>
109
- </tr>
110
-
111
- </tbody>
112
- </table>
113
- </div>
114
- <div class='push'></div><!-- for sticky footer -->
115
- </div><!-- /wrapper -->
116
- <div class='footer quiet pad2 space-top1 center small'>
117
- Code coverage generated by
118
- <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
119
- at 2025-08-26T16:18:42.585Z
120
- </div>
121
- <script src="../prettify.js"></script>
122
- <script>
123
- window.onload = function () {
124
- prettyPrint();
125
- };
126
- </script>
127
- <script src="../sorter.js"></script>
128
- <script src="../block-navigation.js"></script>
129
- </body>
130
- </html>
131
-