@ogcio/o11y-sdk-node 0.4.2 → 0.6.1

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 (140) hide show
  1. package/dist/sdk-core/index.d.ts +1 -0
  2. package/dist/sdk-core/index.js +1 -0
  3. package/dist/sdk-core/lib/index.d.ts +2 -0
  4. package/dist/sdk-core/lib/index.js +2 -0
  5. package/dist/sdk-core/lib/redaction/basic-redactor.d.ts +7 -0
  6. package/dist/sdk-core/lib/redaction/basic-redactor.js +18 -0
  7. package/dist/sdk-core/lib/redaction/email-redactor.d.ts +8 -0
  8. package/dist/sdk-core/lib/redaction/email-redactor.js +17 -0
  9. package/dist/sdk-core/lib/redaction/index.d.ts +9 -0
  10. package/dist/sdk-core/lib/redaction/index.js +4 -0
  11. package/dist/sdk-core/lib/redaction/ip-redactor.d.ts +9 -0
  12. package/dist/sdk-core/lib/redaction/ip-redactor.js +23 -0
  13. package/dist/sdk-core/lib/redaction/ppsn-redactor.d.ts +8 -0
  14. package/dist/sdk-core/lib/redaction/ppsn-redactor.js +17 -0
  15. package/dist/sdk-core/lib/utils/data-structures.d.ts +15 -0
  16. package/dist/{lib/internals/redaction/pii-detection.js → sdk-core/lib/utils/data-structures.js} +4 -27
  17. package/dist/sdk-core/lib/utils/index.d.ts +2 -0
  18. package/dist/sdk-core/lib/utils/index.js +2 -0
  19. package/dist/sdk-core/lib/utils/string-decoding.d.ts +7 -0
  20. package/dist/sdk-core/lib/utils/string-decoding.js +22 -0
  21. package/dist/{index.d.ts → sdk-node/index.d.ts} +1 -0
  22. package/dist/{index.js → sdk-node/index.js} +1 -0
  23. package/dist/{lib → sdk-node/lib}/config-manager.d.ts +1 -1
  24. package/dist/sdk-node/lib/exporter/console.d.ts +3 -0
  25. package/dist/sdk-node/lib/exporter/grpc.d.ts +3 -0
  26. package/dist/sdk-node/lib/exporter/http.d.ts +3 -0
  27. package/dist/{lib → sdk-node/lib}/exporter/index.d.ts +2 -2
  28. package/dist/{lib → sdk-node/lib}/exporter/pii-exporter-decorator.d.ts +5 -5
  29. package/dist/{lib → sdk-node/lib}/exporter/pii-exporter-decorator.js +6 -2
  30. package/dist/sdk-node/lib/exporter/processor-config.d.ts +7 -0
  31. package/dist/{lib → sdk-node/lib}/exporter/processor-config.js +4 -0
  32. package/dist/{lib → sdk-node/lib}/index.d.ts +11 -0
  33. package/dist/{lib → sdk-node/lib}/instrumentation.node.js +1 -1
  34. package/dist/sdk-node/lib/internals/redaction/redactors/email.d.ts +8 -0
  35. package/dist/sdk-node/lib/internals/redaction/redactors/email.js +19 -0
  36. package/dist/sdk-node/lib/internals/redaction/redactors/index.d.ts +16 -0
  37. package/dist/sdk-node/lib/internals/redaction/redactors/index.js +13 -0
  38. package/dist/sdk-node/lib/internals/redaction/redactors/ip.d.ts +8 -0
  39. package/dist/sdk-node/lib/internals/redaction/redactors/ip.js +20 -0
  40. package/dist/sdk-node/lib/internals/redaction/redactors/ppsn.d.ts +8 -0
  41. package/dist/sdk-node/lib/internals/redaction/redactors/ppsn.js +18 -0
  42. package/dist/{lib → sdk-node/lib}/metrics.d.ts +1 -1
  43. package/dist/{lib → sdk-node/lib}/processor/enrich-logger-processor.d.ts +3 -3
  44. package/dist/{lib → sdk-node/lib}/processor/enrich-span-processor.d.ts +3 -3
  45. package/dist/sdk-node/lib/processor/nextjs-logger-processor.d.ts +7 -0
  46. package/dist/sdk-node/lib/processor/nextjs-logger-processor.js +30 -0
  47. package/dist/sdk-node/lib/processor/nextjs-span-processor.d.ts +8 -0
  48. package/dist/sdk-node/lib/processor/nextjs-span-processor.js +25 -0
  49. package/dist/{lib → sdk-node/lib}/resource.d.ts +2 -2
  50. package/dist/{lib → sdk-node/lib}/traces.d.ts +1 -1
  51. package/dist/{lib → sdk-node/lib}/traces.js +1 -1
  52. package/dist/{lib → sdk-node/lib}/url-sampler.d.ts +3 -3
  53. package/dist/{lib → sdk-node/lib}/utils.d.ts +1 -1
  54. package/dist/sdk-node/package.json +62 -0
  55. package/package.json +28 -25
  56. package/CHANGELOG.md +0 -233
  57. package/dist/lib/exporter/console.d.ts +0 -3
  58. package/dist/lib/exporter/grpc.d.ts +0 -3
  59. package/dist/lib/exporter/http.d.ts +0 -3
  60. package/dist/lib/exporter/processor-config.d.ts +0 -5
  61. package/dist/lib/internals/redaction/pii-detection.d.ts +0 -25
  62. package/dist/lib/internals/redaction/redactors/email.d.ts +0 -8
  63. package/dist/lib/internals/redaction/redactors/email.js +0 -48
  64. package/dist/lib/internals/redaction/redactors/index.d.ts +0 -4
  65. package/dist/lib/internals/redaction/redactors/index.js +0 -6
  66. package/dist/lib/internals/redaction/redactors/ip.d.ts +0 -10
  67. package/dist/lib/internals/redaction/redactors/ip.js +0 -54
  68. package/dist/lib/internals/shared-metrics.d.ts +0 -7
  69. package/dist/lib/internals/shared-metrics.js +0 -18
  70. package/dist/package.json +0 -59
  71. package/dist/vitest.config.d.ts +0 -2
  72. package/dist/vitest.config.js +0 -45
  73. package/index.ts +0 -9
  74. package/lib/config-manager.ts +0 -12
  75. package/lib/exporter/console.ts +0 -33
  76. package/lib/exporter/grpc.ts +0 -65
  77. package/lib/exporter/http.ts +0 -56
  78. package/lib/exporter/index.ts +0 -9
  79. package/lib/exporter/pii-exporter-decorator.ts +0 -187
  80. package/lib/exporter/processor-config.ts +0 -23
  81. package/lib/index.ts +0 -118
  82. package/lib/instrumentation.node.ts +0 -115
  83. package/lib/internals/hooks.ts +0 -14
  84. package/lib/internals/redaction/pii-detection.ts +0 -113
  85. package/lib/internals/redaction/redactors/email.ts +0 -58
  86. package/lib/internals/redaction/redactors/index.ts +0 -12
  87. package/lib/internals/redaction/redactors/ip.ts +0 -68
  88. package/lib/internals/shared-metrics.ts +0 -34
  89. package/lib/metrics.ts +0 -75
  90. package/lib/processor/enrich-logger-processor.ts +0 -34
  91. package/lib/processor/enrich-span-processor.ts +0 -39
  92. package/lib/resource.ts +0 -30
  93. package/lib/traces.ts +0 -78
  94. package/lib/url-sampler.ts +0 -52
  95. package/lib/utils.ts +0 -22
  96. package/test/config-manager.test.ts +0 -34
  97. package/test/exporter/pii-exporter-decorator.test.ts +0 -88
  98. package/test/index.test.ts +0 -70
  99. package/test/integration/README.md +0 -74
  100. package/test/integration/docker-utils.sh +0 -214
  101. package/test/integration/main.sh +0 -52
  102. package/test/integration/teardown.sh +0 -7
  103. package/test/integration/test_fastify-o11y-pii-enabled/http-tracing.integration.test.ts +0 -56
  104. package/test/integration/test_fastify-o11y-pii-enabled/pii.integration.test.ts +0 -68
  105. package/test/integration/test_fastify-o11y-pii-enabled/run.sh +0 -42
  106. package/test/integration/test_without-o11y/run.sh +0 -30
  107. package/test/integration/test_without-o11y/verify-status.integration.test.ts +0 -32
  108. package/test/internals/hooks.test.ts +0 -45
  109. package/test/internals/pii-detection.test.ts +0 -265
  110. package/test/internals/redactors/email.test.ts +0 -81
  111. package/test/internals/redactors/ip.test.ts +0 -93
  112. package/test/internals/shared-metrics.test.ts +0 -34
  113. package/test/metrics.test.ts +0 -142
  114. package/test/node-config.test.ts +0 -190
  115. package/test/processor/enrich-logger-processor.test.ts +0 -58
  116. package/test/processor/enrich-span-processor.test.ts +0 -52
  117. package/test/resource.test.ts +0 -33
  118. package/test/traces/active-span.test.ts +0 -26
  119. package/test/traces/with-span.test.ts +0 -356
  120. package/test/url-sampler.test.ts +0 -215
  121. package/test/utils/alloy-log-parser.ts +0 -53
  122. package/test/utils/mock-signals.ts +0 -144
  123. package/test/validation.test.ts +0 -103
  124. package/tsconfig.json +0 -15
  125. package/vitest.config.ts +0 -46
  126. /package/dist/{lib → sdk-node/lib}/config-manager.js +0 -0
  127. /package/dist/{lib → sdk-node/lib}/exporter/console.js +0 -0
  128. /package/dist/{lib → sdk-node/lib}/exporter/grpc.js +0 -0
  129. /package/dist/{lib → sdk-node/lib}/exporter/http.js +0 -0
  130. /package/dist/{lib → sdk-node/lib}/exporter/index.js +0 -0
  131. /package/dist/{lib → sdk-node/lib}/index.js +0 -0
  132. /package/dist/{lib → sdk-node/lib}/instrumentation.node.d.ts +0 -0
  133. /package/dist/{lib → sdk-node/lib}/internals/hooks.d.ts +0 -0
  134. /package/dist/{lib → sdk-node/lib}/internals/hooks.js +0 -0
  135. /package/dist/{lib → sdk-node/lib}/metrics.js +0 -0
  136. /package/dist/{lib → sdk-node/lib}/processor/enrich-logger-processor.js +0 -0
  137. /package/dist/{lib → sdk-node/lib}/processor/enrich-span-processor.js +0 -0
  138. /package/dist/{lib → sdk-node/lib}/resource.js +0 -0
  139. /package/dist/{lib → sdk-node/lib}/url-sampler.js +0 -0
  140. /package/dist/{lib → sdk-node/lib}/utils.js +0 -0
@@ -1,52 +0,0 @@
1
- #!/bin/bash
2
-
3
- set -e
4
- set -o pipefail
5
-
6
- if [ $# -lt 2 ]; then
7
- echo "Usage: $0 <build-id> <root-path>"
8
- exit 1
9
- fi
10
-
11
- BUILD_ID=$1
12
- ROOT_PATH=$2
13
-
14
- NETWORK_NAME="${BUILD_ID}_testnetwork"
15
- SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
16
-
17
- docker network create $NETWORK_NAME
18
-
19
- TEST_SCRIPTS=( $(find . -type f -path "./packages/sdk-node/test/integration/test*/run.sh" | sort) )
20
-
21
- FAILURES=0
22
-
23
- for test_script in "${TEST_SCRIPTS[@]}"; do
24
- TEST_DIR=$(dirname "$test_script")
25
- TEST_NAME=$(basename "$TEST_DIR")
26
-
27
- echo "Running test: $TEST_NAME ($test_script)"
28
-
29
- # Run the test
30
- bash "$test_script" "$BUILD_ID" "$ROOT_PATH" "$NETWORK_NAME"
31
- EXIT_CODE=$?
32
-
33
- if [ $EXIT_CODE -eq 0 ]; then
34
- echo "$test_script completed"
35
- else
36
- echo "❌ $test_script failed with exit code $EXIT_CODE"
37
- FAILURES=$((FAILURES + 1))
38
- fi
39
- echo "------------------------------"
40
- done
41
-
42
- # Remove network and images from docker host
43
- bash "$SCRIPT_DIR/teardown.sh" "$BUILD_ID" "$NETWORK_NAME"
44
-
45
- # Final summary
46
- if [ $FAILURES -eq 0 ]; then
47
- echo "All tests ready!"
48
- exit 0
49
- else
50
- echo "$FAILURES test(s) failed."
51
- exit 1
52
- fi
@@ -1,7 +0,0 @@
1
- #!/bin/bash
2
-
3
- BUILD_ID=$1
4
- NETWORK_NAME=$2
5
-
6
- docker network rm $NETWORK_NAME
7
- docker rmi -f $(docker images --filter "reference=*:$BUILD_ID" -q)
@@ -1,56 +0,0 @@
1
- import { describe, test, assert } from "vitest";
2
- import { parseLog } from "../../utils/alloy-log-parser";
3
- import { readFile } from "node:fs/promises";
4
- import { join } from "node:path";
5
-
6
- describe("instrumentation integration test", () => {
7
- test("should exclude health url and process only dummy calls", async () => {
8
- const data = await readFile(join(__dirname, "logs.txt"), "utf-8");
9
-
10
- let health_traces_counter = 0;
11
- let dummy_traces_counter = 0;
12
-
13
- for (const line of data.split(/\nts=/)) {
14
- const parsedLine: Record<string, object | string | number> =
15
- parseLog(line);
16
-
17
- if (
18
- parsedLine["attributes"] &&
19
- parsedLine["attributes"]["span_kind"] &&
20
- parsedLine["attributes"]["span_kind"] === "trace"
21
- ) {
22
- if (parsedLine["attributes"]["http.target"] === "/api/dummy") {
23
- dummy_traces_counter++;
24
-
25
- // verify global sdk span resource
26
- assert.equal(
27
- parsedLine["resource_attributes"]["team.infra.pod"],
28
- "01",
29
- );
30
- assert.equal(
31
- parsedLine["resource_attributes"]["team.service.type"],
32
- "fastify",
33
- );
34
-
35
- // verify global sdk span attributes
36
- assert.equal(parsedLine["attributes"]["signal.namespace"], "example");
37
-
38
- // verify runtime custom span inside dev application
39
- assert.equal(parsedLine["attributes"]["business.info"], "dummy");
40
- assert.equal(
41
- parsedLine["attributes"]["business.request_type"],
42
- "application/json",
43
- );
44
-
45
- continue;
46
- }
47
- if (parsedLine["attributes"]["http.target"] === "/api/health") {
48
- health_traces_counter++;
49
- }
50
- }
51
- }
52
-
53
- assert.equal(health_traces_counter, 0);
54
- assert.equal(dummy_traces_counter, 2);
55
- });
56
- });
@@ -1,68 +0,0 @@
1
- import { describe, test, assert } from "vitest";
2
- import { parseLog } from "../../utils/alloy-log-parser";
3
- import { readFile } from "node:fs/promises";
4
- import { join } from "node:path";
5
-
6
- describe("pii integration test", () => {
7
- test("should check redacted attributes and detect zero emails in logs", async () => {
8
- const data = await readFile(join(__dirname, "logs.txt"), "utf-8");
9
-
10
- let health_traces_counter = 0;
11
- let dummy_traces_counter = 0;
12
- let email_not_redacted = 0;
13
-
14
- for (const line of data.split(/\nts=/)) {
15
- const parsedLine: Record<string, object | string | number> =
16
- parseLog(line);
17
-
18
- if (
19
- parsedLine["attributes"] &&
20
- parsedLine["attributes"]["span_kind"] &&
21
- parsedLine["attributes"]["span_kind"] === "log"
22
- ) {
23
- const matches = (parsedLine["log_body"] as string).match(
24
- /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g,
25
- );
26
- if (matches) {
27
- email_not_redacted++;
28
- }
29
- }
30
-
31
- if (
32
- parsedLine["attributes"] &&
33
- parsedLine["attributes"]["span_kind"] &&
34
- parsedLine["attributes"]["span_kind"] === "trace"
35
- ) {
36
- if (parsedLine["attributes"]["http.target"] === "/api/dummy") {
37
- dummy_traces_counter++;
38
-
39
- // verify global sdk span resource
40
- assert.equal(
41
- parsedLine["resource_attributes"]["email"],
42
- "[REDACTED EMAIL]",
43
- );
44
-
45
- // verify global sdk span attributes
46
- assert.equal(parsedLine["attributes"]["email"], "[REDACTED EMAIL]");
47
- // verify custom runtime span attributes
48
- assert.equal(
49
- parsedLine["attributes"]["multiple.email"],
50
- "[REDACTED EMAIL]",
51
- );
52
-
53
- // verify global sdk span attributes
54
- assert.equal(parsedLine["events"]["email"], "[REDACTED EMAIL]");
55
-
56
- continue;
57
- }
58
- if (parsedLine["attributes"]["http.target"] === "/api/health") {
59
- health_traces_counter++;
60
- }
61
- }
62
- }
63
-
64
- assert.equal(email_not_redacted, 0);
65
- assert.equal(health_traces_counter, 0);
66
- assert.equal(dummy_traces_counter, 2);
67
- });
68
- });
@@ -1,42 +0,0 @@
1
- #!/bin/bash
2
- # @docs: packages/sdk-node/test/integration/README.md#test_fastify-o11y-pii-enabled
3
- BUILD_ID=$1
4
- ROOT_PATH=$2
5
- NETWORK_NAME=$3
6
-
7
- ALLOY_CONTAINER_NAME="integrationalloy"
8
- NODE_CONTAINER_NAME="${BUILD_ID}_fastify_app"
9
- SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
10
- LOG_FILE="$SCRIPT_DIR/logs.txt"
11
- ERROR_CODE=0
12
-
13
- source "$SCRIPT_DIR/../docker-utils.sh"
14
-
15
- build_image "${NODE_CONTAINER_NAME}:${BUILD_ID}" "$ROOT_PATH/examples/fastify/Dockerfile" "$ROOT_PATH/"
16
-
17
- run_and_check_alloy $ROOT_PATH $NETWORK_NAME $ALLOY_CONTAINER_NAME
18
- ERROR_CODE=$?
19
-
20
- if [[ $ERROR_CODE -eq 0 ]]; then
21
- run_and_check_fastify $NETWORK_NAME "http://integrationalloy:4317" $NODE_CONTAINER_NAME $BUILD_ID
22
- ERROR_CODE=$?
23
- fi
24
-
25
- if [[ $ERROR_CODE -eq 0 ]]; then
26
- sleep 2
27
- curl -X GET -f http://localhost:9091/api/dummy -H 'Content-Type: application/json'
28
- sleep 2
29
- curl -X GET -f http://localhost:9091/api/dummy -H 'Content-Type: application/json'
30
- fi
31
-
32
- # sleep N seconds to await instrumentation flow send and receiving signals
33
- sleep 1
34
-
35
- # Copy logs from container to file
36
- docker container logs $ALLOY_CONTAINER_NAME >&$LOG_FILE
37
- echo "log file at $LOG_FILE"
38
-
39
- container_stop_and_rm $ALLOY_CONTAINER_NAME
40
- container_stop_and_rm $NODE_CONTAINER_NAME
41
-
42
- exit $ERROR_CODE
@@ -1,30 +0,0 @@
1
- #!/bin/bash
2
- # @docs: packages/sdk-node/test/integration/README.md#test_without-o11y
3
- BUILD_ID=$1
4
- ROOT_PATH=$2
5
- NETWORK_NAME=$3
6
-
7
- NODE_CONTAINER_NAME="${BUILD_ID}_fastify_app"
8
- ERROR_CODE=0
9
- SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
10
- LOG_FILE="$SCRIPT_DIR/logs.txt"
11
-
12
- source "$SCRIPT_DIR/../docker-utils.sh"
13
-
14
- build_image "${NODE_CONTAINER_NAME}:${BUILD_ID}" "$ROOT_PATH/examples/fastify/Dockerfile" "$ROOT_PATH/"
15
-
16
- run_and_check_fastify $NETWORK_NAME "" $NODE_CONTAINER_NAME $BUILD_ID
17
- ERROR_CODE=$?
18
-
19
- if [[ $ERROR_CODE -eq 0 ]]; then
20
- curl -X GET -f http://localhost:9091/api/dummy -H 'Content-Type: application/json'
21
- sleep 1
22
- curl -X GET -f http://localhost:9091/api/dummy -H 'Content-Type: application/json'
23
- fi
24
-
25
- docker container logs $NODE_CONTAINER_NAME >&$LOG_FILE
26
- echo "log file at $LOG_FILE"
27
-
28
- container_stop_and_rm $NODE_CONTAINER_NAME
29
-
30
- exit $ERROR_CODE
@@ -1,32 +0,0 @@
1
- import { describe, it, expect } from "vitest";
2
- import { readFile } from "node:fs/promises";
3
- import { join } from "node:path";
4
-
5
- describe("Log validation for /api/dummy with instrumentation disabled", async () => {
6
- const log = await readFile(join(__dirname, "logs.txt"), "utf-8");
7
-
8
- it("should contain message that collector URL is not set", () => {
9
- expect(log).toContain(
10
- "collectorUrl not set. Skipping NodeJS OpenTelemetry instrumentation.",
11
- );
12
- });
13
-
14
- it("should have all /api/dummy responses with status code 200", () => {
15
- // Extract all request IDs for /api/dummy requests
16
- const dummyRequestIds = [
17
- ...log.matchAll(/"reqId":"(.*?)","req":\{[^}]*"url":"\/api\/dummy"/g),
18
- ].map((match) => match[1]);
19
-
20
- expect(dummyRequestIds.length).toBeGreaterThan(0);
21
-
22
- // For each /api/dummy request ID, verify that its response status is 200
23
- const non200Responses = dummyRequestIds.filter((reqId) => {
24
- const responseMatch = log.match(
25
- new RegExp(`"reqId":"${reqId}".*"statusCode":(\\d+)`),
26
- );
27
- return responseMatch && Number(responseMatch[1]) !== 200;
28
- });
29
-
30
- expect(non200Responses).toEqual([]); // No non-200 responses allowed
31
- });
32
- });
@@ -1,45 +0,0 @@
1
- import { describe, it, expect, vi } from "vitest";
2
- import { _shutdownHook } from "../../lib/internals/hooks";
3
-
4
- describe("shutdown hook", () => {
5
- it("should call sdk.shutdown and log success on SIGTERM", async () => {
6
- const shutdownMock = vi.fn().mockResolvedValue(undefined);
7
- const consoleLogMock = vi
8
- .spyOn(console, "log")
9
- .mockImplementation(() => {});
10
-
11
- _shutdownHook({ shutdown: shutdownMock });
12
-
13
- process.emit("SIGTERM");
14
- // Wait for async logic
15
- await Promise.resolve();
16
-
17
- expect(shutdownMock).toHaveBeenCalled();
18
- expect(consoleLogMock).toHaveBeenCalledWith(
19
- "NodeJS OpenTelemetry instrumentation shutdown successfully",
20
- );
21
-
22
- consoleLogMock.mockRestore();
23
- });
24
-
25
- it("should catch and log errors on shutdown failure", async () => {
26
- const error = new Error("Shutdown failed");
27
- const shutdownMock = vi.fn().mockRejectedValue(error);
28
- const consoleErrorMock = vi
29
- .spyOn(console, "error")
30
- .mockImplementation(() => {});
31
-
32
- _shutdownHook({ shutdown: shutdownMock });
33
-
34
- process.emit("SIGTERM");
35
- // Wait for async logic
36
- await Promise.resolve();
37
-
38
- expect(consoleErrorMock).toHaveBeenCalledWith(
39
- "Error shutting down NodeJS OpenTelemetry instrumentation:",
40
- error,
41
- );
42
-
43
- consoleErrorMock.mockRestore();
44
- });
45
- });
@@ -1,265 +0,0 @@
1
- import { describe, expect, it, vi, beforeEach } from "vitest";
2
- import {
3
- _cleanStringPII,
4
- _containsEncodedComponents,
5
- _recursiveObjectClean,
6
- } from "../../lib/internals/redaction/pii-detection.js";
7
- import * as sharedMetrics from "../../lib/internals/shared-metrics.js";
8
- import { emailRedactor } from "../../lib/internals/redaction/redactors/email";
9
- import { ipRedactor } from "../../lib/internals/redaction/redactors/ip";
10
-
11
- describe("PII Detection Utils", () => {
12
- const mockMetricAdd = vi.fn();
13
-
14
- beforeEach(() => {
15
- vi.restoreAllMocks();
16
- vi.spyOn(sharedMetrics, "_getPIICounterRedactionMetric").mockReturnValue({
17
- add: mockMetricAdd,
18
- });
19
- });
20
-
21
- describe("_cleanStringPII", () => {
22
- it("redacts plain PII", () => {
23
- const input = "admin@example.com";
24
- const output = _cleanStringPII(input, "log", [emailRedactor]);
25
-
26
- expect(output).toBe("[REDACTED EMAIL]");
27
- expect(mockMetricAdd).toHaveBeenCalledWith(
28
- 1,
29
- expect.objectContaining({
30
- pii_email_domain: "example.com",
31
- pii_type: "email",
32
- redaction_source: "log",
33
- }),
34
- );
35
- });
36
-
37
- it("redacts email in URL-encoded string", () => {
38
- const input = "user%40gmail.com";
39
- const output = _cleanStringPII(input, "log", [emailRedactor]);
40
-
41
- expect(output).toBe("[REDACTED EMAIL]");
42
- expect(mockMetricAdd).toHaveBeenCalledWith(
43
- 1,
44
- expect.objectContaining({
45
- pii_format: "url",
46
- pii_email_domain: "gmail.com",
47
- }),
48
- );
49
- });
50
-
51
- it("redacts ip in URL-encoded string", () => {
52
- const input = "%20127.0.0.1";
53
- const output = _cleanStringPII(input, "log", [ipRedactor]);
54
-
55
- expect(output).toBe(" [REDACTED IPV4]");
56
- expect(mockMetricAdd).toHaveBeenCalledWith(
57
- 1,
58
- expect.objectContaining({
59
- pii_format: "url",
60
- pii_type: "IPv4",
61
- redaction_source: "log",
62
- }),
63
- );
64
- });
65
-
66
- it("handles strings without PII unchanged", () => {
67
- const input = "hello world";
68
- const output = _cleanStringPII(input, "log", [emailRedactor]);
69
-
70
- expect(output).toBe("hello world");
71
- expect(mockMetricAdd).not.toHaveBeenCalled();
72
- });
73
-
74
- it("ignores non-string input", () => {
75
- // @ts-expect-error
76
- expect(_cleanStringPII(1234, "trace", [emailRedactor])).toBe(1234);
77
- // @ts-expect-error
78
- expect(_cleanStringPII(true, "trace", [emailRedactor])).toBe(true);
79
- expect(
80
- _cleanStringPII(undefined, "trace", [emailRedactor]),
81
- ).toBeUndefined();
82
- expect(mockMetricAdd).not.toHaveBeenCalled();
83
- });
84
- });
85
-
86
- describe("_recursiveObjectClean", () => {
87
- it("cleans string PII", () => {
88
- const result = _recursiveObjectClean("demo@abc.com", "log", [
89
- emailRedactor,
90
- ]);
91
- expect(result).toBe("[REDACTED EMAIL]");
92
- });
93
-
94
- it("cleans array of strings", () => {
95
- const input = ["one@gmail.com", "two@example.com"];
96
- const output = _recursiveObjectClean(input, "log", [emailRedactor]);
97
-
98
- expect(output).toEqual(["[REDACTED EMAIL]", "[REDACTED EMAIL]"]);
99
- expect(mockMetricAdd).toHaveBeenCalledTimes(2);
100
- });
101
-
102
- it("cleans deeply nested object", () => {
103
- const input = {
104
- user: {
105
- email: "test@gmail.com",
106
- profile: {
107
- contact: "foo@example.com",
108
- },
109
- },
110
- status: "active",
111
- };
112
-
113
- const result = _recursiveObjectClean(input, "log", [emailRedactor]);
114
-
115
- expect(result).toEqual({
116
- user: {
117
- email: "[REDACTED EMAIL]",
118
- profile: {
119
- contact: "[REDACTED EMAIL]",
120
- },
121
- },
122
- status: "active",
123
- });
124
- });
125
-
126
- it("cleans Uint8Array input", () => {
127
- const str = "admin@gmail.com";
128
- const buffer = new TextEncoder().encode(str);
129
- const result = _recursiveObjectClean(buffer, "log", [emailRedactor]);
130
- const decoded = new TextDecoder().decode(result as Uint8Array);
131
-
132
- expect(decoded).toBe("[REDACTED EMAIL]");
133
- });
134
-
135
- it("skips malformed Uint8Array decode", () => {
136
- const corrupted = new Uint8Array([0xff, 0xfe, 0xfd]);
137
- const result = _recursiveObjectClean(corrupted, "log", [emailRedactor]);
138
-
139
- // Should return a Uint8Array, but unmodified/redaction should not happen
140
- expect(result).toBeInstanceOf(Uint8Array);
141
- expect(result).not.toEqual(expect.arrayContaining([91, 82, 69]));
142
- });
143
-
144
- it("cleans arrays of values", () => {
145
- const result = _recursiveObjectClean(
146
- ["bob@abc.com", 123, { nested: "jane@example.com" }],
147
- "log",
148
- [emailRedactor],
149
- );
150
-
151
- expect(result).toEqual([
152
- "[REDACTED EMAIL]",
153
- 123,
154
- { nested: "[REDACTED EMAIL]" },
155
- ]);
156
- });
157
-
158
- it("passes null and boolean through", () => {
159
- expect(_recursiveObjectClean(null, "log", [emailRedactor])).toBeNull();
160
- expect(
161
- _recursiveObjectClean(undefined, "log", [emailRedactor]),
162
- ).toBeUndefined();
163
- expect(_recursiveObjectClean(true, "log", [emailRedactor])).toBe(true);
164
- expect(_recursiveObjectClean(false, "log", [emailRedactor])).toBe(false);
165
- });
166
- });
167
-
168
- describe("_containsEncodedComponents", () => {
169
- describe("should return true for properly URL encoded strings", () => {
170
- it.each([
171
- ["hello%20world", "Space encoded as %20"],
172
- ["test%2Bvalue", "Plus sign encoded as %2B"],
173
- ["path%2Fto%2Ffile", "Forward slashes encoded"],
174
- ["user%40domain.com", "@ symbol encoded"],
175
- ["100%25%20off", "Percent and space encoded"],
176
- ["a%3Db%26c%3Dd", "Query parameters (a=b&c=d)"],
177
- ["caf%C3%A9", "UTF-8 encoded (café)"],
178
- ["price%3A%20%2410", "Colon, space, dollar ($10)"],
179
- ["%22quoted%22", "Double quotes encoded"],
180
- ["https%3A%2F%2Fexample.com", "Full URL encoded"],
181
- ["file%20name.txt", "filename encoded"],
182
- ["search%3Fq%3Dhello%20world", "Query string encoded"],
183
- ["%3C%3E%26%22%27", "HTML special chars encoded (<>&\"')"],
184
- ["%E2%9C%93", "UTF-8 checkmark (✓) encoded"],
185
- ["test%2b", "Lowercase hex"],
186
- ["%E4%B8%AD%E6%96%87", "Chinese characters encoded"],
187
- ])('should detect "%s" as URL encoded (%s)', (input, description) => {
188
- expect(_containsEncodedComponents(input)).toBe(true);
189
- });
190
- });
191
-
192
- describe("should return false for non-URL encoded strings", () => {
193
- it.each([
194
- ["test", "Simple ASCII string"],
195
- ["hello world", "Unencoded space"],
196
- ["user@domain.com", "Unencoded email"],
197
- ["simple123", "Alphanumeric only"],
198
- ["", "Empty string"],
199
- ["25%%", "Literal percent signs"],
200
- ["100% off", "Percent without hex digits"],
201
- ["test%2", "Incomplete percent encoding"],
202
- ["hello%ZZ", "Invalid hex digits"],
203
- ["test%2G", "Invalid hex digit G"],
204
- ["bad%encoding%here", "Percent without hex pairs"],
205
- ["hello%20world and more", "Partially encoded string"],
206
- ["hello%20world%21%20how%20are%20you", "Overly encoded string"],
207
- ["café", "Unicode characters (unencoded)"],
208
- ["hello+world", "Plus sign (form encoding style)"],
209
- ["test%", "Trailing percent"],
210
- ["hello%20world%", "Encoded content with trailing percent"],
211
- ["%", "Single percent"],
212
- ["%%", "Double percent"],
213
- ["normal text with % symbols", "Text with percent but no encoding"],
214
- ["price: $100%", "Currency with percent"],
215
- ["file.txt", "Simple filename"],
216
- ["path/to/file", "Unencoded path"],
217
- ["query?param=value", "Unencoded query string"],
218
- ["hello%world", "Percent without following hex"],
219
- ["test%1", "Single hex digit after percent"],
220
- ["hello%zz", "Non-hex characters after percent"],
221
- ])('should not detect "%s" as URL encoded (%s)', (input, description) => {
222
- expect(_containsEncodedComponents(input)).toBe(false);
223
- });
224
- });
225
-
226
- describe("Error handling", () => {
227
- it.each([
228
- ["%C0%80", "Overlong UTF-8 encoding (security concern)"],
229
- ["%ED%A0%80", "UTF-8 surrogate (invalid)"],
230
- ["%FF%FE", "Invalid UTF-8 sequence"],
231
- ["test%20%ZZ", "Mix of valid and invalid encoding"],
232
- ])('should handle "%s" (%s)', (input, description) => {
233
- // These should not throw errors
234
- expect(() => _containsEncodedComponents(input)).not.toThrow();
235
-
236
- // Most of these should return false due to invalid sequences
237
- // or security-related encoding issues
238
- const result = _containsEncodedComponents(input);
239
- expect(typeof result).toBe("boolean");
240
- });
241
- });
242
-
243
- describe("real-world url examples", () => {
244
- it.each([
245
- [
246
- "https%3A%2F%2Fgoogle.com%2Fsearch%3Fq%3Djavascript",
247
- "Encoded Google search URL",
248
- ],
249
- ["The%20quick%20brown%20fox", "Sentence with spaces encoded"],
250
- [
251
- "/api/user?body=here%20are%20all%20my%20secrets",
252
- "contextually encoded API path",
253
- ],
254
- ["redirect_uri=https%3A%2F%2Fapp.com%2Fcallback", "OAuth redirect URI"],
255
- ["data%3Atext%2Fplain%3Bbase64%2CSGVsbG8%3D", "Data URL encoded"],
256
- ])('real-world case: "%s" (%s)', (input, description) => {
257
- const result = _containsEncodedComponents(input);
258
-
259
- // Verify the function doesn't crash
260
- expect(typeof result).toBe("boolean");
261
- expect(result).toBe(true);
262
- });
263
- });
264
- });
265
- });
@@ -1,81 +0,0 @@
1
- import {
2
- describe,
3
- expect,
4
- it,
5
- vi,
6
- beforeEach,
7
- beforeAll,
8
- afterAll,
9
- } from "vitest";
10
-
11
- import * as sharedMetrics from "../../../lib/internals/shared-metrics.js";
12
- import { ipRedactor } from "../../../lib/internals/redaction/redactors/ip";
13
- import { emailRedactor } from "../../../lib/internals/redaction/redactors/email";
14
-
15
- describe("Email Redaction utils", () => {
16
- describe("tracks metrics", () => {
17
- const mockMetricAdd = vi.fn();
18
-
19
- beforeEach(() => {
20
- vi.restoreAllMocks();
21
- vi.spyOn(sharedMetrics, "_getPIICounterRedactionMetric").mockReturnValue({
22
- add: mockMetricAdd,
23
- });
24
- });
25
-
26
- it("redacts plain PII and tracks redaction with metric", () => {
27
- const input = "admin@example.com";
28
- const output = emailRedactor(input, "log", "string");
29
-
30
- expect(output).toBe("[REDACTED EMAIL]");
31
- expect(mockMetricAdd).toHaveBeenCalledWith(
32
- 1,
33
- expect.objectContaining({
34
- pii_email_domain: "example.com",
35
- pii_type: "email",
36
- redaction_source: "log",
37
- }),
38
- );
39
- });
40
-
41
- it("handles strings without PII unchanged", () => {
42
- const input = "hello world";
43
- const output = ipRedactor(input, "log", "string");
44
-
45
- expect(output).toBe("hello world");
46
- expect(mockMetricAdd).not.toHaveBeenCalled();
47
- });
48
- });
49
-
50
- describe("Redacts email addresses", () => {
51
- beforeAll(() => {
52
- vi.spyOn(sharedMetrics, "_getPIICounterRedactionMetric").mockReturnValue({
53
- add: vi.fn(),
54
- });
55
- });
56
-
57
- afterAll(() => {
58
- vi.restoreAllMocks();
59
- });
60
-
61
- it.each`
62
- value | expectedRedactedValue
63
- ${"user+tag@example.com"} | ${"[REDACTED EMAIL]"}
64
- ${"user.name+tag+sorting@example.com"} | ${"[REDACTED EMAIL]"}
65
- ${"x@example.museum"} | ${"[REDACTED EMAIL]"}
66
- ${"a.b-c_d@example.co.uk"} | ${"[REDACTED EMAIL]"}
67
- ${"üser@example.de"} | ${"[REDACTED EMAIL]"}
68
- ${"john.doe@xn--exmple-cua.com"} | ${"[REDACTED EMAIL]"}
69
- ${"üser@example.de"} | ${"[REDACTED EMAIL]"}
70
- ${"plainaddress"} | ${"plainaddress"}
71
- ${"@missinglocal.org"} | ${"@missinglocal.org"}
72
- ${"user@invalid_domain.com"} | ${"user@invalid_domain.com"}
73
- `(
74
- "returns $expectedRedactedValue for value '$value'",
75
- async ({ value, expectedRedactedValue }: Record<string, string>) => {
76
- const result = emailRedactor(value, "log", "string");
77
- expect(result).toBe(expectedRedactedValue);
78
- },
79
- );
80
- });
81
- });