Skip to content

Commit 23f37db

Browse files
committed
test: Add integration tests for common-utils/metadata
1 parent e5c77da commit 23f37db

File tree

7 files changed

+228
-2
lines changed

7 files changed

+228
-2
lines changed

Makefile

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,10 +45,16 @@ dev-int:
4545
npx nx run @hyperdx/api:dev:int $(FILE)
4646
docker compose -p int -f ./docker-compose.ci.yml down
4747

48+
.PHONY: dev-int-common-utils
49+
dev-int-common-utils:
50+
docker compose -p int -f ./docker-compose.ci.yml up -d
51+
npx nx run @hyperdx/common-utils:dev:int $(FILE)
52+
docker compose -p int -f ./docker-compose.ci.yml down
53+
4854
.PHONY: ci-int
4955
ci-int:
5056
docker compose -p int -f ./docker-compose.ci.yml up -d
51-
npx nx run @hyperdx/api:ci:int
57+
npx nx run-many -t ci:int --parallel=false
5258
docker compose -p int -f ./docker-compose.ci.yml down
5359

5460
.PHONY: dev-unit

packages/common-utils/.env.test

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
CLICKHOUSE_HOST=http://localhost:8123
2+
CLICKHOUSE_PASSWORD=
3+
CLICKHOUSE_USER=default
4+
NODE_ENV=test

packages/common-utils/jest.config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ module.exports = {
55
verbose: true,
66
rootDir: './src',
77
testMatch: ['**/__tests__/*.test.ts?(x)'],
8+
testPathIgnorePatterns: ['.*\\.int\\.test\\.ts$'],
89
testTimeout: 30000,
910
moduleNameMapper: {
1011
'@/(.*)$': '<rootDir>/$1',
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
/** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */
2+
module.exports = {
3+
setupFiles: ['dotenv/config'],
4+
preset: 'ts-jest',
5+
testEnvironment: 'node',
6+
verbose: true,
7+
rootDir: './src',
8+
testMatch: ['**/__tests__/*.int.test.ts?(x)'],
9+
testTimeout: 30000,
10+
moduleNameMapper: {
11+
'@/(.*)$': '<rootDir>/$1',
12+
},
13+
};

packages/common-utils/package.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
"@types/sqlstring": "^2.3.0",
3737
"@types/supertest": "^2.0.12",
3838
"@types/uuid": "^8.3.4",
39+
"dotenv": "^17.2.3",
3940
"jest": "^28.1.1",
4041
"nodemon": "^2.0.20",
4142
"rimraf": "^4.4.1",
@@ -56,6 +57,8 @@
5657
"lint:fix": "npx eslint . --ext .ts --fix",
5758
"ci:lint": "yarn lint && yarn tsc --noEmit",
5859
"ci:unit": "jest --runInBand --ci --forceExit --coverage",
59-
"dev:unit": "jest --watchAll --runInBand --detectOpenHandles"
60+
"dev:unit": "jest --watchAll --runInBand --detectOpenHandles",
61+
"ci:int": "DOTENV_CONFIG_PATH=.env.test jest --config jest.int.config.js --runInBand --ci --forceExit --coverage",
62+
"dev:int": "DOTENV_CONFIG_PATH=.env.test jest --config jest.int.config.js --watchAll --runInBand --detectOpenHandles"
6063
}
6164
}
Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
import { createClient } from '@clickhouse/client';
2+
import { ClickHouseClient } from '@clickhouse/client-common';
3+
4+
import { ClickhouseClient as HdxClickhouseClient } from '@/clickhouse/node';
5+
import { Metadata, MetadataCache } from '@/metadata';
6+
import { ChartConfigWithDateRange } from '@/types';
7+
8+
describe('Metadata Integration Tests', () => {
9+
let client: ClickHouseClient;
10+
let hdxClient: HdxClickhouseClient;
11+
12+
beforeAll(() => {
13+
const host = process.env.CLICKHOUSE_HOST || 'http://localhost:8123';
14+
const username = process.env.CLICKHOUSE_USER || 'default';
15+
const password = process.env.CLICKHOUSE_PASSWORD || '';
16+
17+
client = createClient({
18+
url: host,
19+
username,
20+
password,
21+
});
22+
23+
hdxClient = new HdxClickhouseClient({
24+
host,
25+
username,
26+
password,
27+
});
28+
});
29+
30+
describe('getKeyValues', () => {
31+
let metadata: Metadata;
32+
const chartConfig: ChartConfigWithDateRange = {
33+
connection: 'test_connection',
34+
from: {
35+
databaseName: 'default',
36+
tableName: 'test_table',
37+
},
38+
dateRange: [new Date('2023-01-01'), new Date('2025-01-01')],
39+
select: 'col1, col2',
40+
timestampValueExpression: 'Timestamp',
41+
where: '',
42+
};
43+
44+
beforeAll(async () => {
45+
await client.command({
46+
query: `CREATE OR REPLACE TABLE default.test_table (
47+
Timestamp DateTime64(9) CODEC(Delta(8), ZSTD(1)),
48+
SeverityText LowCardinality(String) CODEC(ZSTD(1)),
49+
TraceId String,
50+
LogAttributes JSON CODEC(ZSTD(1)),
51+
ResourceAttributes Map(LowCardinality(String), String) CODEC(ZSTD(1)),
52+
\`__hdx_materialized_k8s.pod.name\` String MATERIALIZED ResourceAttributes['k8s.pod.name'] CODEC(ZSTD(1)),
53+
)
54+
ENGINE = MergeTree()
55+
ORDER BY (Timestamp)
56+
`,
57+
});
58+
59+
await client.command({
60+
query: `INSERT INTO default.test_table (Timestamp, SeverityText, TraceId, ResourceAttributes, LogAttributes) VALUES
61+
('2023-06-01 12:00:00', 'info', '1o2udn120d8n', { 'k8s.pod.name': 'pod1', 'env': 'prod' },'{"action":"ping"}'),
62+
('2024-06-01 12:00:00', 'error', '67890-09098', { 'k8s.pod.name': 'pod2', 'env': 'prod' },'{}'),
63+
('2024-06-01 12:00:00', 'info', '11h9238re1h92', { 'env': 'staging' },'{"user":"john"}'),
64+
('2024-06-01 12:00:00', 'warning', '1o2udn120d8n', { 'k8s.pod.name': 'pod1', 'env': 'prod' }, '{"user":"jack","action":"login"}'),
65+
('2024-06-01 12:00:00', '', '1o2udn120d8n', { 'env': 'prod' }, '{"user":"jane","action":"login"}')
66+
`,
67+
});
68+
});
69+
70+
beforeEach(async () => {
71+
metadata = new Metadata(hdxClient, new MetadataCache());
72+
});
73+
74+
afterAll(async () => {
75+
await client.command({
76+
query: 'DROP TABLE IF EXISTS default.test_table',
77+
});
78+
});
79+
80+
describe.each([true, false])('with disableRowLimit=%s', disableRowLimit => {
81+
it('should return key-value pairs for a given metadata key', async () => {
82+
const resultSeverityText = await metadata.getKeyValues({
83+
chartConfig,
84+
keys: ['SeverityText'],
85+
disableRowLimit,
86+
});
87+
88+
expect(resultSeverityText).toHaveLength(1);
89+
expect(resultSeverityText[0].key).toBe('SeverityText');
90+
expect(resultSeverityText[0].value).toHaveLength(3);
91+
expect(resultSeverityText[0].value).toEqual(
92+
expect.arrayContaining(['info', 'error', 'warning']),
93+
);
94+
95+
const resultTraceId = await metadata.getKeyValues({
96+
chartConfig,
97+
keys: ['TraceId'],
98+
disableRowLimit,
99+
});
100+
101+
expect(resultTraceId).toHaveLength(1);
102+
expect(resultTraceId[0].key).toBe('TraceId');
103+
expect(resultTraceId[0].value).toHaveLength(3);
104+
expect(resultTraceId[0].value).toEqual(
105+
expect.arrayContaining([
106+
'1o2udn120d8n',
107+
'67890-09098',
108+
'11h9238re1h92',
109+
]),
110+
);
111+
112+
const resultBoth = await metadata.getKeyValues({
113+
chartConfig,
114+
keys: ['TraceId', 'SeverityText'],
115+
disableRowLimit,
116+
});
117+
118+
expect(resultBoth).toEqual([
119+
{
120+
key: 'TraceId',
121+
value: expect.arrayContaining([
122+
'1o2udn120d8n',
123+
'67890-09098',
124+
'11h9238re1h92',
125+
]),
126+
},
127+
{
128+
key: 'SeverityText',
129+
value: expect.arrayContaining(['info', 'error', 'warning']),
130+
},
131+
]);
132+
});
133+
134+
it('should handle materialized columns correctly', async () => {
135+
const resultPodName = await metadata.getKeyValues({
136+
chartConfig,
137+
keys: ['__hdx_materialized_k8s.pod.name'],
138+
disableRowLimit,
139+
});
140+
141+
expect(resultPodName).toHaveLength(1);
142+
expect(resultPodName[0].key).toBe('__hdx_materialized_k8s.pod.name');
143+
expect(resultPodName[0].value).toHaveLength(2);
144+
expect(resultPodName[0].value).toEqual(
145+
expect.arrayContaining(['pod1', 'pod2']),
146+
);
147+
});
148+
149+
it('should handle JSON columns correctly', async () => {
150+
const resultLogAttributes = await metadata.getKeyValues({
151+
chartConfig,
152+
keys: ['LogAttributes.user'],
153+
disableRowLimit,
154+
});
155+
156+
expect(resultLogAttributes).toHaveLength(1);
157+
expect(resultLogAttributes[0].key).toBe('LogAttributes.user');
158+
expect(resultLogAttributes[0].value).toHaveLength(3);
159+
expect(resultLogAttributes[0].value).toEqual(
160+
expect.arrayContaining(['john', 'jack', 'jane']),
161+
);
162+
});
163+
164+
it('should return an empty list when no keys are provided', async () => {
165+
const resultEmpty = await metadata.getKeyValues({
166+
chartConfig,
167+
keys: [],
168+
});
169+
170+
expect(resultEmpty).toEqual([]);
171+
});
172+
173+
it('should correctly limit the number of returned values', async () => {
174+
const resultLimited = await metadata.getKeyValues({
175+
chartConfig,
176+
keys: ['SeverityText'],
177+
limit: 2,
178+
});
179+
180+
expect(resultLimited).toHaveLength(1);
181+
expect(resultLimited[0].key).toBe('SeverityText');
182+
expect(resultLimited[0].value).toHaveLength(2);
183+
expect(
184+
resultLimited[0].value.every(v =>
185+
['info', 'error', 'warning'].includes(v),
186+
),
187+
).toBeTruthy();
188+
});
189+
});
190+
});
191+
});

yarn.lock

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4536,6 +4536,7 @@ __metadata:
45364536
"@types/uuid": "npm:^8.3.4"
45374537
date-fns: "npm:^2.28.0"
45384538
date-fns-tz: "npm:^2.0.0"
4539+
dotenv: "npm:^17.2.3"
45394540
jest: "npm:^28.1.1"
45404541
lodash: "npm:^4.17.21"
45414542
node-sql-parser: "npm:^5.3.5"
@@ -14691,6 +14692,13 @@ __metadata:
1469114692
languageName: node
1469214693
linkType: hard
1469314694

14695+
"dotenv@npm:^17.2.3":
14696+
version: 17.2.3
14697+
resolution: "dotenv@npm:17.2.3"
14698+
checksum: 10c0/c884403209f713214a1b64d4d1defa4934c2aa5b0002f5a670ae298a51e3c3ad3ba79dfee2f8df49f01ae74290fcd9acdb1ab1d09c7bfb42b539036108bb2ba0
14699+
languageName: node
14700+
linkType: hard
14701+
1469414702
"dunder-proto@npm:^1.0.1":
1469514703
version: 1.0.1
1469614704
resolution: "dunder-proto@npm:1.0.1"

0 commit comments

Comments
 (0)