@ogcio/o11y-sdk-node 0.1.0-beta.4 → 0.1.0-beta.7

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 (71) hide show
  1. package/CHANGELOG.md +22 -1
  2. package/README.md +59 -0
  3. package/dist/index.d.ts +2 -2
  4. package/dist/index.js +1 -0
  5. package/dist/lib/exporter/console.d.ts +3 -0
  6. package/dist/lib/exporter/console.js +16 -0
  7. package/dist/lib/exporter/grpc.d.ts +3 -0
  8. package/dist/lib/{grpc.js → exporter/grpc.js} +9 -5
  9. package/dist/lib/exporter/http.d.ts +3 -0
  10. package/dist/lib/{http.js → exporter/http.js} +9 -5
  11. package/dist/lib/index.d.ts +28 -5
  12. package/dist/lib/instrumentation.node.js +37 -7
  13. package/dist/lib/options.d.ts +5 -3
  14. package/dist/lib/processor/enrich-span-processor.d.ts +11 -0
  15. package/dist/lib/processor/enrich-span-processor.js +22 -0
  16. package/dist/lib/traces.d.ts +1 -0
  17. package/dist/lib/traces.js +4 -0
  18. package/dist/lib/url-sampler.d.ts +10 -0
  19. package/dist/lib/url-sampler.js +25 -0
  20. package/dist/lib/utils.d.ts +4 -2
  21. package/dist/lib/utils.js +8 -3
  22. package/dist/package.json +20 -17
  23. package/dist/vitest.config.js +15 -1
  24. package/index.ts +2 -2
  25. package/lib/exporter/console.ts +27 -0
  26. package/lib/{grpc.ts → exporter/grpc.ts} +13 -7
  27. package/lib/{http.ts → exporter/http.ts} +13 -7
  28. package/lib/index.ts +36 -4
  29. package/lib/instrumentation.node.ts +59 -9
  30. package/lib/options.ts +5 -3
  31. package/lib/processor/enrich-span-processor.ts +39 -0
  32. package/lib/traces.ts +5 -0
  33. package/lib/url-sampler.ts +52 -0
  34. package/lib/utils.ts +16 -4
  35. package/package.json +20 -17
  36. package/test/enrich-span-processor.test.ts +105 -0
  37. package/test/index.test.ts +9 -0
  38. package/test/integration/README.md +26 -0
  39. package/test/integration/integration.test.ts +58 -0
  40. package/test/integration/run.sh +85 -0
  41. package/test/node-config.test.ts +49 -36
  42. package/test/url-sampler.test.ts +215 -0
  43. package/test/utils/alloy-log-parser.ts +46 -0
  44. package/vitest.config.ts +15 -1
  45. package/coverage/cobertura-coverage.xml +0 -310
  46. package/coverage/lcov-report/base.css +0 -224
  47. package/coverage/lcov-report/block-navigation.js +0 -87
  48. package/coverage/lcov-report/favicon.png +0 -0
  49. package/coverage/lcov-report/index.html +0 -131
  50. package/coverage/lcov-report/prettify.css +0 -1
  51. package/coverage/lcov-report/prettify.js +0 -2
  52. package/coverage/lcov-report/sdk-node/index.html +0 -116
  53. package/coverage/lcov-report/sdk-node/index.ts.html +0 -112
  54. package/coverage/lcov-report/sdk-node/lib/console.ts.html +0 -130
  55. package/coverage/lcov-report/sdk-node/lib/grpc.ts.html +0 -178
  56. package/coverage/lcov-report/sdk-node/lib/http.ts.html +0 -190
  57. package/coverage/lcov-report/sdk-node/lib/index.html +0 -221
  58. package/coverage/lcov-report/sdk-node/lib/index.ts.html +0 -256
  59. package/coverage/lcov-report/sdk-node/lib/instrumentation.node.ts.html +0 -334
  60. package/coverage/lcov-report/sdk-node/lib/metrics.ts.html +0 -295
  61. package/coverage/lcov-report/sdk-node/lib/options.ts.html +0 -106
  62. package/coverage/lcov-report/sdk-node/lib/utils.ts.html +0 -115
  63. package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
  64. package/coverage/lcov-report/sorter.js +0 -196
  65. package/coverage/lcov.info +0 -312
  66. package/dist/lib/console.d.ts +0 -3
  67. package/dist/lib/console.js +0 -12
  68. package/dist/lib/grpc.d.ts +0 -3
  69. package/dist/lib/http.d.ts +0 -3
  70. package/lib/console.ts +0 -15
  71. package/test-report.xml +0 -71
@@ -0,0 +1,85 @@
1
+ BUILD_ID=$1
2
+ ROOT_PATH=$2
3
+
4
+ NETWORK_NAME="${BUILD_ID}_testnetwork"
5
+ ALLOY_CONTAINER_NAME="integrationalloy"
6
+ NODE_CONTAINER_NAME="${BUILD_ID}_fastify_app"
7
+ ERROR_CODE=0
8
+
9
+ docker build -t ${NODE_CONTAINER_NAME}:${BUILD_ID} -f $ROOT_PATH/examples/fastify/Dockerfile $ROOT_PATH/
10
+
11
+ docker network create $NETWORK_NAME
12
+
13
+ docker run -d \
14
+ -v "$ROOT_PATH/alloy/integration-test.alloy:/etc/alloy/config.alloy:ro" \
15
+ --network $NETWORK_NAME \
16
+ --name $ALLOY_CONTAINER_NAME \
17
+ -p 4317:4317 \
18
+ -p 4318:4318 \
19
+ grafana/alloy \
20
+ run --server.http.listen-addr=0.0.0.0:12345 --stability.level=experimental /etc/alloy/config.alloy
21
+
22
+ MAX_RETRIES=10
23
+ COUNTER=0
24
+ echo "$ALLOY_CONTAINER_NAME container status"
25
+ until [ "$(docker inspect -f {{.State.Running}} $ALLOY_CONTAINER_NAME)" = true ]; do
26
+ sleep 2
27
+ docker inspect -f {{.State.Running}} $ALLOY_CONTAINER_NAME
28
+ COUNTER=$((COUNTER + 1))
29
+ if [ $COUNTER -ge $MAX_RETRIES ]; then
30
+ echo "Exceeded maximum retries. Exiting."
31
+ ERROR_CODE=1
32
+ break
33
+ fi
34
+ done
35
+
36
+ if [[ $ERROR_CODE -eq 0 ]]; then
37
+ docker run --detach \
38
+ --network $NETWORK_NAME \
39
+ --name $NODE_CONTAINER_NAME \
40
+ --health-cmd="curl -f http://${NODE_CONTAINER_NAME}:9091/api/health > /dev/null || exit 1" \
41
+ --health-start-period=1s \
42
+ --health-retries=10 \
43
+ --health-interval=1s \
44
+ -p 9091:9091 \
45
+ ${NODE_CONTAINER_NAME}:${BUILD_ID}
46
+
47
+ COUNTER=0
48
+ echo "$NODE_CONTAINER_NAME container status"
49
+ until [ "$(docker inspect -f {{.State.Health.Status}} $NODE_CONTAINER_NAME)" = "healthy" ]; do
50
+ sleep 1
51
+ docker inspect -f {{.State.Health.Status}} $NODE_CONTAINER_NAME
52
+ COUNTER=$((COUNTER + 1))
53
+ if [ $COUNTER -ge $MAX_RETRIES ]; then
54
+ echo "Exceeded maximum retries. Exiting."
55
+ ERROR_CODE=1
56
+ break
57
+ fi
58
+ done
59
+ fi
60
+
61
+ if [[ $ERROR_CODE -eq 0 ]]; then
62
+ sleep 2
63
+ curl -X GET -f http://localhost:9091/api/dummy -H 'Content-Type: application/json'
64
+ sleep 2
65
+ curl -X GET -f http://localhost:9091/api/dummy -H 'Content-Type: application/json'
66
+ fi
67
+
68
+ # sleep N seconds to await instrumentation flow send and receiving signals
69
+ sleep 10
70
+
71
+ # Copy logs from container to file
72
+ docker container logs $ALLOY_CONTAINER_NAME >&$ROOT_PATH/packages/sdk-node/test/integration/logs.txt
73
+ echo "log file at $ROOT_PATH/packages/sdk-node/test/integration/logs.txt"
74
+
75
+ docker container stop $ALLOY_CONTAINER_NAME
76
+ docker container stop $NODE_CONTAINER_NAME
77
+
78
+ docker container rm -f $ALLOY_CONTAINER_NAME
79
+ docker container rm -f $NODE_CONTAINER_NAME
80
+
81
+ docker image rm ${NODE_CONTAINER_NAME}:${BUILD_ID}
82
+
83
+ docker network rm $NETWORK_NAME
84
+
85
+ exit $ERROR_CODE
@@ -9,7 +9,17 @@ import { OTLPTraceExporter as HTTP_OTLPTraceExporter } from "@opentelemetry/expo
9
9
  import { OTLPMetricExporter as HTTP_OTLPMetricExporter } from "@opentelemetry/exporter-metrics-otlp-http";
10
10
  import { OTLPLogExporter as HTTP_OTLPLogExporter } from "@opentelemetry/exporter-logs-otlp-http";
11
11
  import { NodeSDKConfig } from "../lib/index.js";
12
- import buildHttpExporters from "../lib/http.js";
12
+ import buildHttpExporters from "../lib/exporter/http.js";
13
+ import {
14
+ BatchSpanProcessor,
15
+ ConsoleSpanExporter,
16
+ SimpleSpanProcessor,
17
+ } from "@opentelemetry/sdk-trace-base";
18
+ import {
19
+ BatchLogRecordProcessor,
20
+ SimpleLogRecordProcessor,
21
+ } from "@opentelemetry/sdk-logs";
22
+ import { EnrichSpanProcessor } from "../lib/processor/enrich-span-processor.js";
13
23
 
14
24
  describe("verify config settings", () => {
15
25
  const commonConfig = {
@@ -21,6 +31,7 @@ describe("verify config settings", () => {
21
31
  const config: NodeSDKConfig = {
22
32
  ...commonConfig,
23
33
  protocol: "grpc",
34
+ collectorMode: "batch",
24
35
  diagLogLevel: "NONE",
25
36
  };
26
37
 
@@ -31,20 +42,21 @@ describe("verify config settings", () => {
31
42
  const _configuration = sdk["_configuration"];
32
43
  assert.equal(_configuration.serviceName, commonConfig.serviceName);
33
44
 
34
- const traceExporter = _configuration.traceExporter;
45
+ const logs = _configuration.logRecordProcessors;
35
46
 
36
- assert.ok(traceExporter instanceof GRPC_OTLPTraceExporter);
47
+ assert.equal(logs.length, 1);
48
+ assert.ok(logs[0] instanceof BatchLogRecordProcessor);
49
+
50
+ const spans = _configuration.spanProcessors;
51
+
52
+ assert.equal(spans.length, 2);
53
+ assert.ok(spans[0] instanceof BatchSpanProcessor);
54
+ assert.ok(spans[1] instanceof EnrichSpanProcessor);
37
55
 
38
- const logRecordProcessors = _configuration.logRecordProcessors;
39
- assert.equal(logRecordProcessors.length, 1);
40
- // assert default signals sending mode
41
- assert.ok(logRecordProcessors[0] instanceof logs.BatchLogRecordProcessor);
42
56
  assert.ok(
43
- logRecordProcessors[0]["_exporter"] instanceof GRPC_OTLPLogExporter,
57
+ _configuration.metricReader instanceof
58
+ metrics.PeriodicExportingMetricReader,
44
59
  );
45
-
46
- const metricReader = _configuration.metricReader;
47
- assert.ok(metricReader._exporter instanceof GRPC_OTLPMetricExporter);
48
60
  });
49
61
 
50
62
  test("http config", () => {
@@ -60,19 +72,21 @@ describe("verify config settings", () => {
60
72
  const _configuration = sdk["_configuration"];
61
73
  assert.equal(_configuration.serviceName, commonConfig.serviceName);
62
74
 
63
- const traceExporter = _configuration.traceExporter;
64
- assert.ok(traceExporter instanceof HTTP_OTLPTraceExporter);
75
+ const logs = _configuration.logRecordProcessors;
76
+
77
+ assert.equal(logs.length, 1);
78
+ assert.ok(logs[0] instanceof BatchLogRecordProcessor);
79
+
80
+ const spans = _configuration.spanProcessors;
81
+
82
+ assert.equal(spans.length, 2);
83
+ assert.ok(spans[0] instanceof BatchSpanProcessor);
84
+ assert.ok(spans[1] instanceof EnrichSpanProcessor);
65
85
 
66
- const logRecordProcessors = _configuration.logRecordProcessors;
67
- assert.equal(logRecordProcessors.length, 1);
68
- // assert default signals sending mode
69
- assert.ok(logRecordProcessors[0] instanceof logs.BatchLogRecordProcessor);
70
86
  assert.ok(
71
- logRecordProcessors[0]["_exporter"] instanceof HTTP_OTLPLogExporter,
87
+ _configuration.metricReader instanceof
88
+ metrics.PeriodicExportingMetricReader,
72
89
  );
73
-
74
- const metricReader = _configuration.metricReader;
75
- assert.ok(metricReader._exporter instanceof HTTP_OTLPMetricExporter);
76
90
  });
77
91
 
78
92
  test("console - console config", () => {
@@ -82,30 +96,29 @@ describe("verify config settings", () => {
82
96
  diagLogLevel: "NONE",
83
97
  };
84
98
 
85
- const sdk: NodeSDK | undefined = buildNodeInstrumentation(config);
99
+ const sdk: NodeSDK = buildNodeInstrumentation(config)!;
86
100
  assert.ok(sdk);
87
101
 
88
102
  const _configuration = sdk["_configuration"];
89
103
  assert.equal(_configuration.serviceName, commonConfig.serviceName);
90
104
 
91
- const traceExporter = _configuration.traceExporter;
105
+ const logs = _configuration.logRecordProcessors;
92
106
 
93
- assert.ok(traceExporter instanceof tracing.ConsoleSpanExporter);
94
- assert.isUndefined(traceExporter._transport);
107
+ // verify simple log processor for instant console logging
108
+ assert.equal(logs.length, 1);
109
+ assert.ok(logs[0] instanceof SimpleLogRecordProcessor);
95
110
 
96
- const logRecordProcessors = _configuration.logRecordProcessors;
97
- assert.equal(logRecordProcessors.length, 1);
111
+ const spans = _configuration.spanProcessors;
112
+
113
+ assert.equal(spans.length, 2);
114
+ // verify simple span for instant console logging
115
+ assert.ok(spans[0] instanceof SimpleSpanProcessor);
116
+ assert.ok(spans[1] instanceof EnrichSpanProcessor);
98
117
 
99
- assert.ok(logRecordProcessors[0] instanceof logs.SimpleLogRecordProcessor);
100
118
  assert.ok(
101
- logRecordProcessors[0]["_exporter"] instanceof
102
- logs.ConsoleLogRecordExporter,
119
+ _configuration.metricReader instanceof
120
+ metrics.PeriodicExportingMetricReader,
103
121
  );
104
- assert.isUndefined(logRecordProcessors[0]["_exporter"]._transport);
105
-
106
- const metricReader = _configuration.metricReader;
107
- assert.ok(metricReader._exporter instanceof metrics.ConsoleMetricExporter);
108
- assert.isUndefined(metricReader._exporter._transport);
109
122
  });
110
123
 
111
124
  test("single log sending config", () => {
@@ -124,7 +137,7 @@ describe("verify config settings", () => {
124
137
 
125
138
  const logRecordProcessors = _configuration.logRecordProcessors;
126
139
  assert.equal(logRecordProcessors.length, 1);
127
- assert.ok(logRecordProcessors[0] instanceof logs.SimpleLogRecordProcessor);
140
+ assert.ok(logRecordProcessors[0] instanceof SimpleLogRecordProcessor);
128
141
  });
129
142
 
130
143
  test("check if clear base endpoint final slash", () => {
@@ -0,0 +1,215 @@
1
+ import { Context } from "@opentelemetry/api";
2
+ import {
3
+ SamplingDecision,
4
+ SamplingResult,
5
+ } from "@opentelemetry/sdk-trace-base";
6
+ import { describe, expect, test, vi } from "vitest";
7
+ import { UrlSampler } from "../lib/url-sampler";
8
+
9
+ describe("url sampler", () => {
10
+ // mock sampler to be sure every trace after UrlSamper has RECORD status
11
+ const mockSampler = {
12
+ shouldSample: vi
13
+ .fn()
14
+ .mockImplementation(
15
+ (_context, _traceId, _spanName, _spanKind, attributes, _links) => {
16
+ return {
17
+ decision: SamplingDecision.RECORD,
18
+ attributes: attributes,
19
+ } as SamplingResult;
20
+ },
21
+ ),
22
+ };
23
+
24
+ test("should add custom span attributes to trace", async () => {
25
+ const sampler: UrlSampler = new UrlSampler(
26
+ [
27
+ {
28
+ type: "endsWith",
29
+ url: "/health",
30
+ },
31
+ ],
32
+ mockSampler,
33
+ {
34
+ "signal.namespace": "unittest",
35
+ "signal.callback.result": () => "test",
36
+ },
37
+ );
38
+
39
+ expect(sampler).not.toBeNull();
40
+
41
+ const result = sampler.shouldSample(
42
+ {} as Context,
43
+ "traceId",
44
+ "span",
45
+ 0,
46
+ { "http.target": "/track" },
47
+ [],
48
+ );
49
+
50
+ expect(sampler.toString()).toBe("UrlSampler");
51
+ expect(result.decision).toBe(SamplingDecision.RECORD);
52
+ expect(result.attributes).not.toBeNull();
53
+ });
54
+
55
+ test("should not record trace about /health api", async () => {
56
+ const sampler: UrlSampler = new UrlSampler(
57
+ [
58
+ {
59
+ type: "endsWith",
60
+ url: "/health",
61
+ },
62
+ ],
63
+ mockSampler,
64
+ );
65
+
66
+ const result = sampler.shouldSample(
67
+ {} as Context,
68
+ "traceId",
69
+ "span",
70
+ 0,
71
+ { "http.target": "/health" },
72
+ [],
73
+ );
74
+
75
+ expect(sampler.toString()).toBe("UrlSampler");
76
+ expect(result.decision).toBe(SamplingDecision.NOT_RECORD);
77
+ });
78
+
79
+ test("should record every other trace which is not /health api", async () => {
80
+ const sampler: UrlSampler = new UrlSampler(
81
+ [
82
+ {
83
+ type: "endsWith",
84
+ url: "/health",
85
+ },
86
+ ],
87
+ mockSampler,
88
+ );
89
+
90
+ let result = sampler.shouldSample(
91
+ {} as Context,
92
+ "traceId",
93
+ "span",
94
+ 0,
95
+ { "http.target": "/test" },
96
+ [],
97
+ );
98
+
99
+ expect(result.decision).toBe(SamplingDecision.RECORD);
100
+
101
+ result = sampler.shouldSample(
102
+ {} as Context,
103
+ "traceId",
104
+ "span",
105
+ 0,
106
+ { "http.target": "/another/url" },
107
+ [],
108
+ );
109
+
110
+ expect(result.decision).toBe(SamplingDecision.RECORD);
111
+ });
112
+
113
+ test("operator 'includes', should not record every trace which include /block in url", async () => {
114
+ const sampler: UrlSampler = new UrlSampler(
115
+ [
116
+ {
117
+ type: "includes",
118
+ url: "/block",
119
+ },
120
+ ],
121
+ mockSampler,
122
+ );
123
+
124
+ expect(sampler).not.toBeNull();
125
+
126
+ const result = sampler.shouldSample(
127
+ {} as Context,
128
+ "traceId",
129
+ "span",
130
+ 0,
131
+ { "http.target": "/namespace/block/example/12" },
132
+ [],
133
+ );
134
+
135
+ expect(sampler.toString()).toBe("UrlSampler");
136
+ expect(result.decision).toBe(SamplingDecision.NOT_RECORD);
137
+ });
138
+
139
+ test("operator 'endsWith', should not record only trace which ends with /block in url", async () => {
140
+ const sampler: UrlSampler = new UrlSampler(
141
+ [
142
+ {
143
+ type: "endsWith",
144
+ url: "/block",
145
+ },
146
+ ],
147
+ mockSampler,
148
+ );
149
+
150
+ expect(sampler).not.toBeNull();
151
+
152
+ // expect traced with block in the URL middle
153
+ let result = sampler.shouldSample(
154
+ {} as Context,
155
+ "traceId",
156
+ "span",
157
+ 0,
158
+ { "http.target": "/namespace/block/example/12" },
159
+ [],
160
+ );
161
+
162
+ expect(sampler.toString()).toBe("UrlSampler");
163
+ expect(result.decision).toBe(SamplingDecision.RECORD);
164
+
165
+ // should stop trace with block at the end
166
+ result = sampler.shouldSample(
167
+ {} as Context,
168
+ "traceId",
169
+ "span",
170
+ 0,
171
+ { "http.target": "/namespace/example/block" },
172
+ [],
173
+ );
174
+
175
+ expect(sampler.toString()).toBe("UrlSampler");
176
+ expect(result.decision).toBe(SamplingDecision.NOT_RECORD);
177
+ });
178
+
179
+ test("operator 'equals', should not record trace which is equal to /block in url", async () => {
180
+ const sampler: UrlSampler = new UrlSampler(
181
+ [
182
+ {
183
+ type: "equals",
184
+ url: "/block",
185
+ },
186
+ ],
187
+ mockSampler,
188
+ );
189
+
190
+ expect(sampler).not.toBeNull();
191
+
192
+ let result = sampler.shouldSample(
193
+ {} as Context,
194
+ "traceId",
195
+ "span",
196
+ 0,
197
+ { "http.target": "/namespace/block/example/12" },
198
+ [],
199
+ );
200
+
201
+ expect(sampler.toString()).toBe("UrlSampler");
202
+ expect(result.decision).toBe(SamplingDecision.RECORD);
203
+
204
+ result = sampler.shouldSample(
205
+ {} as Context,
206
+ "traceId",
207
+ "span",
208
+ 0,
209
+ { "http.target": "/block" },
210
+ [],
211
+ );
212
+
213
+ expect(result.decision).toBe(SamplingDecision.NOT_RECORD);
214
+ });
215
+ });
@@ -0,0 +1,46 @@
1
+ export function parseLog(
2
+ log: string,
3
+ ): Record<string, object | string | number> {
4
+ const logArray = log
5
+ .split("\\n")
6
+ .map((line) => line.trim())
7
+ .filter((line) => line);
8
+
9
+ const jsonObject: Record<string, object | string | number> = {};
10
+ let currentSection: Record<string, object | string | number> = jsonObject;
11
+ const sectionStack: Record<string, object | string | number>[] = [];
12
+
13
+ logArray.forEach((line) => {
14
+ line = line.trim();
15
+
16
+ if (line.startsWith("->")) {
17
+ const match = line.match(/->\s+([^:]+):\s+(Str|Int)\((.+)\)/);
18
+ if (match) {
19
+ const [, key, type, value] = match;
20
+ const parsedValue = type === "Int" ? parseInt(value, 10) : value;
21
+
22
+ if (typeof currentSection === "object") {
23
+ currentSection[key] = parsedValue;
24
+ }
25
+ }
26
+ } else if (line.endsWith(":")) {
27
+ // new section
28
+ const sectionName = line
29
+ .slice(0, -1)
30
+ .trim()
31
+ .toLowerCase()
32
+ .replace(" ", "_");
33
+ jsonObject[sectionName] = {};
34
+ currentSection = jsonObject[sectionName] as Record<
35
+ string,
36
+ object | string | number
37
+ >;
38
+ sectionStack.push(currentSection);
39
+ } else if (line.startsWith('"')) {
40
+ // Additional metadata at the end, store it separately
41
+ jsonObject["metadata"] = line;
42
+ }
43
+ });
44
+
45
+ return jsonObject;
46
+ }
package/vitest.config.ts CHANGED
@@ -4,7 +4,6 @@ export default defineConfig({
4
4
  test: {
5
5
  globals: true,
6
6
  watch: false,
7
- include: ["**/test/*.test.ts"],
8
7
  exclude: ["**/fixtures/**", "**/dist/**"],
9
8
  poolOptions: {
10
9
  threads: {
@@ -22,5 +21,20 @@ export default defineConfig({
22
21
  },
23
22
  reporters: ["default", ["junit", { outputFile: "test-report.xml" }]],
24
23
  environment: "node",
24
+ pool: "threads",
25
+ workspace: [
26
+ {
27
+ test: {
28
+ include: ["**/test/*.test.ts"],
29
+ name: "unit",
30
+ },
31
+ },
32
+ {
33
+ test: {
34
+ include: ["**/test/integration/*.test.ts"],
35
+ name: "integration",
36
+ },
37
+ },
38
+ ],
25
39
  },
26
40
  });