Skip to content

Commit 4fc74e4

Browse files
feat: times (#80)
1 parent 81e4ee5 commit 4fc74e4

File tree

6 files changed

+78
-57
lines changed

6 files changed

+78
-57
lines changed

docs/jest-otel/syntax/db-redis.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ So, a complete assertion can look like:
2828

2929
```js
3030
expectTrace(traceloop.serviceByName('redis-service'))
31-
.toSendRedisCommend()
31+
.toSendRedisCommend({ times: 2 }) // optional times parameter (defaults to one)
3232
.withDatabaseName('redis-db')
3333
.withStatement(
3434
/^HGET/

packages/expect-opentelemetry/src/index.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,17 @@ const serviceMatchers = {
2929
toSendRedisCommand,
3030
};
3131

32+
interface MatcherOptions {
33+
times: number;
34+
}
35+
3236
interface TraceMatchers {
3337
toReceiveHttpRequest(): HttpRequest;
3438
toSendHttpRequest(): HttpRequest;
35-
toQueryPostgreSQL(): PostgreSQLQuery;
39+
toQueryPostgreSQL(options?: MatcherOptions): PostgreSQLQuery;
3640
toReceiveGrpcRequest(): GrpcRequest;
3741
toSendGrpcRequest(): GrpcRequest;
38-
toSendRedisCommand(): RedisCommand;
42+
toSendRedisCommand(options?: MatcherOptions): RedisCommand;
3943
}
4044

4145
function createMatcher(matcher, type) {

packages/expect-opentelemetry/src/matchers/service/to-query-postgresql.ts

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,25 +3,32 @@ import { Service } from '../../resources/service';
33
import { opentelemetry } from '@traceloop/otel-proto';
44
import { PostgreSQLQuery } from '../../resources/postgresql-query';
55

6-
export function toQueryPostgreSQL(service: Service): PostgreSQLQuery {
6+
export function toQueryPostgreSQL(
7+
service: Service,
8+
options = { times: 1 },
9+
): PostgreSQLQuery {
710
const { name: serviceName, spans } = service;
811

912
const filteredSpans = spans.filter((span) => {
1013
return (
1114
span.kind ===
1215
opentelemetry.proto.trace.v1.Span.SpanKind.SPAN_KIND_CLIENT &&
13-
span.attributes?.find((attribute) => {
14-
return (
15-
attribute.key === SemanticAttributes.DB_SYSTEM &&
16-
attribute.value?.stringValue === 'postgresql'
17-
);
18-
})
16+
span.attributes?.find(
17+
(attribute: opentelemetry.proto.common.v1.IKeyValue) => {
18+
return (
19+
attribute.key === SemanticAttributes.DB_SYSTEM &&
20+
attribute.value?.stringValue === 'postgresql'
21+
);
22+
},
23+
)
1924
);
2025
});
2126

22-
if (filteredSpans.length === 0) {
23-
throw new Error(`No query by ${serviceName} to postgresql was found`);
27+
if (filteredSpans.length < options.times) {
28+
throw new Error(
29+
`Expected ${options.times} queries by ${serviceName} to postgresql, but found ${filteredSpans.length}.`,
30+
);
2431
}
2532

26-
return new PostgreSQLQuery(filteredSpans, serviceName);
33+
return new PostgreSQLQuery(filteredSpans, serviceName, options.times);
2734
}

packages/expect-opentelemetry/src/matchers/service/to-send-redis-command.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@ import { SemanticAttributes } from '@opentelemetry/semantic-conventions';
22
import { opentelemetry } from '@traceloop/otel-proto';
33
import { RedisCommand, Service } from '../../resources';
44

5-
export function toSendRedisCommand(service: Service): RedisCommand {
5+
export function toSendRedisCommand(
6+
service: Service,
7+
options = { times: 1 },
8+
): RedisCommand {
69
const { name: serviceName, spans } = service;
710

811
const filteredSpans = spans.filter((span) => {
@@ -20,9 +23,11 @@ export function toSendRedisCommand(service: Service): RedisCommand {
2023
);
2124
});
2225

23-
if (filteredSpans.length === 0) {
24-
throw new Error(`No redis command from ${serviceName} found`);
26+
if (filteredSpans.length < options.times) {
27+
throw new Error(
28+
`Expected ${options.times} queries by ${serviceName} to redis, but found ${filteredSpans.length}.`,
29+
);
2530
}
2631

27-
return new RedisCommand(filteredSpans, serviceName);
32+
return new RedisCommand(filteredSpans, serviceName, options.times);
2833
}

packages/expect-opentelemetry/src/resources/postgresql-query.ts

Lines changed: 40 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ export class PostgreSQLQuery {
1212
constructor(
1313
readonly spans: opentelemetry.proto.trace.v1.ISpan[],
1414
private readonly serviceName: string,
15+
private readonly times = 1,
1516
) {}
1617

1718
withDatabaseName(name: string | RegExp, options?: CompareOptions) {
@@ -22,18 +23,19 @@ export class PostgreSQLQuery {
2223
options,
2324
);
2425

25-
if (filteredSpans.length === 0) {
26-
throw new Error(
27-
`No query by ${
28-
this.serviceName
29-
} to postgresql with database name ${name} was found. Found db names: ${extractAttributeStringValues(
30-
this.spans,
31-
SemanticAttributes.DB_NAME,
32-
)}`,
33-
);
26+
if (filteredSpans.length < this.times) {
27+
throw new Error(`Expected ${this.times} queries by ${
28+
this.serviceName
29+
} to postgresql with database name ${name}, but found ${
30+
filteredSpans.length
31+
}.\n
32+
Found db names:\n ${extractAttributeStringValues(
33+
this.spans,
34+
SemanticAttributes.DB_NAME,
35+
)}`);
3436
}
3537

36-
return new PostgreSQLQuery(filteredSpans, this.serviceName);
38+
return new PostgreSQLQuery(filteredSpans, this.serviceName, this.times);
3739
}
3840

3941
withStatement(statement: string | RegExp, options?: CompareOptions) {
@@ -44,18 +46,16 @@ export class PostgreSQLQuery {
4446
options,
4547
);
4648

47-
if (filteredSpans.length === 0) {
48-
throw new Error(
49-
`No query by ${
50-
this.serviceName
51-
} to postgresql with statement ${statement} was found. Found statements:\n ${extractAttributeStringValues(
52-
this.spans,
53-
SemanticAttributes.DB_STATEMENT,
54-
).join('\n\n')}`,
55-
);
49+
if (filteredSpans.length < this.times) {
50+
throw new Error(`Expected ${this.times} queries by ${
51+
this.serviceName
52+
} to postgresql with statement ${statement}, but found ${
53+
filteredSpans.length
54+
}.\n
55+
Found statements:\n${printStatements(this.spans)}`);
5656
}
5757

58-
return new PostgreSQLQuery(filteredSpans, this.serviceName);
58+
return new PostgreSQLQuery(filteredSpans, this.serviceName, this.times);
5959
}
6060

6161
withOperations(...operations: string[]) {
@@ -76,18 +76,14 @@ export class PostgreSQLQuery {
7676
);
7777
});
7878

79-
if (filteredSpans.length === 0) {
79+
if (filteredSpans.length < this.times) {
8080
throw new Error(
81-
`No query by ${
82-
this.serviceName
83-
} to postgresql with operations ${operations} was found. Found statements: ${extractAttributeStringValues(
84-
this.spans,
85-
SemanticAttributes.DB_STATEMENT,
86-
).join('\n\n')}`,
81+
`Expected ${this.times} queries by ${this.serviceName} to postgresql with operations ${operations}, but found ${filteredSpans.length}.\n` +
82+
`Found statements:\n${printStatements(this.spans)}`,
8783
);
8884
}
8985

90-
return new PostgreSQLQuery(filteredSpans, this.serviceName);
86+
return new PostgreSQLQuery(filteredSpans, this.serviceName, this.times);
9187
}
9288

9389
withTables(...tables: string[]) {
@@ -117,17 +113,25 @@ export class PostgreSQLQuery {
117113
);
118114
});
119115

120-
if (filteredSpans.length === 0) {
116+
if (filteredSpans.length < this.times) {
121117
throw new Error(
122-
`No query by ${
123-
this.serviceName
124-
} to postgresql with tables ${tables} was found. Found statements: ${extractAttributeStringValues(
125-
this.spans,
126-
SemanticAttributes.DB_STATEMENT,
127-
).join('\n\n')}`,
118+
`Expected ${this.times} queries by ${this.serviceName} to postgresql with tables ${tables}, but found ${filteredSpans.length}.\n` +
119+
`Found statements:\n${printStatements(this.spans)}`,
128120
);
129121
}
130122

131-
return new PostgreSQLQuery(filteredSpans, this.serviceName);
123+
return new PostgreSQLQuery(filteredSpans, this.serviceName, this.times);
132124
}
133125
}
126+
127+
const printStatements = (spans: opentelemetry.proto.trace.v1.ISpan[]) => {
128+
const MAX_LEN = 100;
129+
return extractAttributeStringValues(spans, SemanticAttributes.DB_STATEMENT)
130+
.map((statement) => {
131+
if (statement.length > MAX_LEN) {
132+
return `${statement.slice(0, MAX_LEN)}...`;
133+
}
134+
return statement;
135+
})
136+
.join('\n');
137+
};

packages/expect-opentelemetry/src/resources/redis-command.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ export class RedisCommand {
99
constructor(
1010
readonly spans: opentelemetry.proto.trace.v1.ISpan[],
1111
private readonly serviceName: string,
12+
private readonly times = 1,
1213
) {}
1314

1415
withDatabaseName(name: string | RegExp, options: CompareOptions) {
@@ -19,9 +20,9 @@ export class RedisCommand {
1920
options,
2021
);
2122

22-
if (filteredSpans.length === 0) {
23+
if (filteredSpans.length < this.times) {
2324
throw new Error(
24-
`No redis command from service ${this.serviceName} to database ${name} found`,
25+
`Expected ${this.times} queries by ${this.serviceName} to redis with database name ${name}, but found ${filteredSpans.length}.`,
2526
);
2627
}
2728

@@ -36,9 +37,9 @@ export class RedisCommand {
3637
options,
3738
);
3839

39-
if (filteredSpans.length === 0) {
40+
if (filteredSpans.length < this.times) {
4041
throw new Error(
41-
`No redis command with statement ${statement} from service ${this.serviceName} found`,
42+
`Expected ${this.times} queries by ${this.serviceName} to redis with statement ${statement}, but found ${filteredSpans.length}.`,
4243
);
4344
}
4445

0 commit comments

Comments
 (0)