Skip to content

Commit 547bc42

Browse files
authored
More config v6 tests (#102)
* Add test for another special case of segment condition logging * Add test for multi-level flag dependency * Add tests for comparison attribute and comparison value trimming * Use valid SDK keys in cache key generation tests * Add tests for comparison attribute conversion to canonical string representation * Simplify EvaluateContext + improve perf. of user attribute retrieval during evaluation + reduce allocations * Set eslint as default formatter for VSCode workspace * Minor corrections * Move isStringArray into Utils * Make line break char sequence configurable in log messages * Align config json error handling of EvaluateLogBuilder with error reporting of RolloutEvaluator * Improve naming * Improve User Object tests * Add a few more test cases * Correct visibility of appendTargetingRuleThenPart * Minor corrections * Correct grammar mistake * Make naming of UserComparator member consistent * Add tests for some number parsing edge cases * Adjust terminology to docs (eliminate the usage of term 'match' in the context of conditions) * Bump version
1 parent 68e2306 commit 547bc42

22 files changed

+2982
-168
lines changed

.vscode/settings.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
{
22
"editor.rulers": [160],
3+
"[typescript]": {
4+
"editor.defaultFormatter": "dbaeumer.vscode-eslint"
5+
},
36
"eslint.format.enable": true,
47
"eslint.validate": [
58
"typescript"

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "configcat-common",
3-
"version": "9.2.0",
3+
"version": "9.3.0",
44
"description": "ConfigCat is a configuration as a service that lets you manage your features and configurations without actually deploying new code.",
55
"main": "lib/index.js",
66
"types": "lib/index.d.ts",

src/ConfigCatLogger.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,9 @@ export interface IConfigCatLogger {
6464
/** Gets the log level (the minimum level to use for filtering log events). */
6565
readonly level?: LogLevel;
6666

67+
/** Gets the character sequence to use for line breaks in log messages. Defaults to "\n". */
68+
readonly eol?: string;
69+
6770
/**
6871
* Writes an event into the log.
6972
* @param level Event severity level.
@@ -79,6 +82,10 @@ export class LoggerWrapper implements IConfigCatLogger {
7982
return this.logger.level ?? LogLevel.Warn;
8083
}
8184

85+
get eol(): string {
86+
return this.logger.eol ?? "\n";
87+
}
88+
8289
constructor(
8390
private readonly logger: IConfigCatLogger,
8491
private readonly hooks?: SafeHooksWrapper) {
@@ -369,7 +376,7 @@ export class ConfigCatConsoleLogger implements IConfigCatLogger {
369376
/**
370377
* Create an instance of ConfigCatConsoleLogger
371378
*/
372-
constructor(public level = LogLevel.Warn) {
379+
constructor(public level = LogLevel.Warn, readonly eol = "\n") {
373380
}
374381

375382
/** @inheritdoc */
@@ -381,7 +388,7 @@ export class ConfigCatConsoleLogger implements IConfigCatLogger {
381388
level === LogLevel.Error ? [console.error, "ERROR"] :
382389
[console.log, LogLevel[level].toUpperCase()];
383390

384-
const exceptionString = exception !== void 0 ? "\n" + errorToString(exception, true) : "";
391+
const exceptionString = exception !== void 0 ? this.eol + errorToString(exception, true) : "";
385392

386393
logMethod(`${this.SOURCE} - ${levelString} - [${eventId}] ${message}${exceptionString}`);
387394
}

src/ConfigJson.ts

Lines changed: 52 additions & 52 deletions
Large diffs are not rendered by default.

src/EvaluateLogBuilder.ts

Lines changed: 46 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { PrerequisiteFlagComparator, SegmentComparator, UserComparator } from "./ConfigJson";
2-
import type { PrerequisiteFlagCondition, SegmentCondition, SettingValue, TargetingRule, UserCondition, UserConditionUnion } from "./ProjectConfig";
2+
import type { PrerequisiteFlagCondition, SegmentCondition, Setting, SettingValue, TargetingRule, UserCondition, UserConditionUnion } from "./ProjectConfig";
33
import { isAllowedValue } from "./RolloutEvaluator";
4-
import { formatStringList, isArray } from "./Utils";
4+
import { formatStringList, isArray, isStringArray } from "./Utils";
55

66
const invalidValuePlaceholder = "<invalid value>";
77
const invalidNamePlaceholder = "<invalid name>";
@@ -14,6 +14,9 @@ export class EvaluateLogBuilder {
1414
private log = "";
1515
private indent = "";
1616

17+
constructor(private readonly eol: string) {
18+
}
19+
1720
resetIndent(): this {
1821
this.indent = "";
1922
return this;
@@ -30,7 +33,7 @@ export class EvaluateLogBuilder {
3033
}
3134

3235
newLine(text?: string): this {
33-
this.log += "\n" + this.indent + (text ?? "");
36+
this.log += this.eol + this.indent + (text ?? "");
3437
return this;
3538
}
3639

@@ -43,20 +46,20 @@ export class EvaluateLogBuilder {
4346
return this.log;
4447
}
4548

46-
appendEvaluationResult(isMatch: boolean): this {
47-
return this.append(`${isMatch}`);
48-
}
49-
5049
private appendUserConditionCore(comparisonAttribute: string, comparator: UserComparator, comparisonValue?: unknown) {
5150
return this.append(`User.${comparisonAttribute} ${formatUserComparator(comparator)} '${comparisonValue ?? invalidValuePlaceholder}'`);
5251
}
5352

5453
private appendUserConditionString(comparisonAttribute: string, comparator: UserComparator, comparisonValue: string, isSensitive: boolean) {
54+
if (typeof comparisonValue !== "string") {
55+
return this.appendUserConditionCore(comparisonAttribute, comparator);
56+
}
57+
5558
return this.appendUserConditionCore(comparisonAttribute, comparator, !isSensitive ? comparisonValue : "<hashed value>");
5659
}
5760

5861
private appendUserConditionStringList(comparisonAttribute: string, comparator: UserComparator, comparisonValue: ReadonlyArray<string>, isSensitive: boolean): this {
59-
if (comparisonValue == null) {
62+
if (!isStringArray(comparisonValue)) {
6063
return this.appendUserConditionCore(comparisonAttribute, comparator);
6164
}
6265

@@ -74,7 +77,7 @@ export class EvaluateLogBuilder {
7477
}
7578

7679
private appendUserConditionNumber(comparisonAttribute: string, comparator: UserComparator, comparisonValue: number, isDateTime?: boolean) {
77-
if (comparisonValue == null) {
80+
if (typeof comparisonValue !== "number") {
7881
return this.appendUserConditionCore(comparisonAttribute, comparator);
7982
}
8083

@@ -86,12 +89,14 @@ export class EvaluateLogBuilder {
8689
}
8790

8891
appendUserCondition(condition: UserConditionUnion): this {
89-
const { comparisonAttribute, comparator } = condition;
92+
const comparisonAttribute = typeof condition.comparisonAttribute === "string" ? condition.comparisonAttribute : invalidNamePlaceholder;
93+
const comparator = condition.comparator;
94+
9095
switch (condition.comparator) {
91-
case UserComparator.IsOneOf:
92-
case UserComparator.IsNotOneOf:
93-
case UserComparator.ContainsAnyOf:
94-
case UserComparator.NotContainsAnyOf:
96+
case UserComparator.TextIsOneOf:
97+
case UserComparator.TextIsNotOneOf:
98+
case UserComparator.TextContainsAnyOf:
99+
case UserComparator.TextNotContainsAnyOf:
95100
case UserComparator.SemVerIsOneOf:
96101
case UserComparator.SemVerIsNotOneOf:
97102
case UserComparator.TextStartsWithAnyOf:
@@ -118,8 +123,8 @@ export class EvaluateLogBuilder {
118123
case UserComparator.NumberGreaterOrEquals:
119124
return this.appendUserConditionNumber(comparisonAttribute, comparator, condition.comparisonValue);
120125

121-
case UserComparator.SensitiveIsOneOf:
122-
case UserComparator.SensitiveIsNotOneOf:
126+
case UserComparator.SensitiveTextIsOneOf:
127+
case UserComparator.SensitiveTextIsNotOneOf:
123128
case UserComparator.SensitiveTextStartsWithAnyOf:
124129
case UserComparator.SensitiveTextNotStartsWithAnyOf:
125130
case UserComparator.SensitiveTextEndsWithAnyOf:
@@ -141,8 +146,12 @@ export class EvaluateLogBuilder {
141146
}
142147
}
143148

144-
appendPrerequisiteFlagCondition(condition: PrerequisiteFlagCondition): this {
145-
const prerequisiteFlagKey = condition.prerequisiteFlagKey;
149+
appendPrerequisiteFlagCondition(condition: PrerequisiteFlagCondition, settings: Readonly<{ [name: string]: Setting }>): this {
150+
const prerequisiteFlagKey =
151+
typeof condition.prerequisiteFlagKey !== "string" ? invalidNamePlaceholder :
152+
!(condition.prerequisiteFlagKey in settings) ? invalidReferencePlaceholder :
153+
condition.prerequisiteFlagKey;
154+
146155
const comparator = condition.comparator;
147156
const comparisonValue = condition.comparisonValue;
148157

@@ -153,18 +162,24 @@ export class EvaluateLogBuilder {
153162
const segment = condition.segment;
154163
const comparator = condition.comparator;
155164

156-
const segmentName = segment?.name ??
157-
(segment == null ? invalidReferencePlaceholder : invalidNamePlaceholder);
165+
const segmentName =
166+
segment == null ? invalidReferencePlaceholder :
167+
typeof segment.name !== "string" || !segment.name ? invalidNamePlaceholder :
168+
segment.name;
158169

159170
return this.append(`User ${formatSegmentComparator(comparator)} '${segmentName}'`);
160171
}
161172

162-
appendConditionConsequence(isMatch: boolean): this {
163-
this.append(" => ").appendEvaluationResult(isMatch);
164-
return isMatch ? this : this.append(", skipping the remaining AND conditions");
173+
appendConditionResult(result: boolean): this {
174+
return this.append(`${result}`);
175+
}
176+
177+
appendConditionConsequence(result: boolean): this {
178+
this.append(" => ").appendConditionResult(result);
179+
return result ? this : this.append(", skipping the remaining AND conditions");
165180
}
166181

167-
appendTargetingRuleThenPart(targetingRule: TargetingRule, newLine: boolean): this {
182+
private appendTargetingRuleThenPart(targetingRule: TargetingRule, newLine: boolean): this {
168183
(newLine ? this.newLine() : this.append(" "))
169184
.append("THEN");
170185

@@ -184,14 +199,14 @@ export class EvaluateLogBuilder {
184199

185200
export function formatUserComparator(comparator: UserComparator): string {
186201
switch (comparator) {
187-
case UserComparator.IsOneOf:
188-
case UserComparator.SensitiveIsOneOf:
202+
case UserComparator.TextIsOneOf:
203+
case UserComparator.SensitiveTextIsOneOf:
189204
case UserComparator.SemVerIsOneOf: return "IS ONE OF";
190-
case UserComparator.IsNotOneOf:
191-
case UserComparator.SensitiveIsNotOneOf:
205+
case UserComparator.TextIsNotOneOf:
206+
case UserComparator.SensitiveTextIsNotOneOf:
192207
case UserComparator.SemVerIsNotOneOf: return "IS NOT ONE OF";
193-
case UserComparator.ContainsAnyOf: return "CONTAINS ANY OF";
194-
case UserComparator.NotContainsAnyOf: return "NOT CONTAINS ANY OF";
208+
case UserComparator.TextContainsAnyOf: return "CONTAINS ANY OF";
209+
case UserComparator.TextNotContainsAnyOf: return "NOT CONTAINS ANY OF";
195210
case UserComparator.SemVerLess:
196211
case UserComparator.NumberLess: return "<";
197212
case UserComparator.SemVerLessOrEquals:
@@ -225,7 +240,7 @@ export function formatUserComparator(comparator: UserComparator): string {
225240
}
226241

227242
export function formatUserCondition(condition: UserConditionUnion): string {
228-
return new EvaluateLogBuilder().appendUserCondition(condition).toString();
243+
return new EvaluateLogBuilder("").appendUserCondition(condition).toString();
229244
}
230245

231246
export function formatPrerequisiteFlagComparator(comparator: PrerequisiteFlagComparator): string {

src/ProjectConfig.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -272,10 +272,10 @@ export interface ICondition<TCondition extends keyof ConditionTypeMap = keyof Co
272272
export type ConditionUnion = UserConditionUnion | PrerequisiteFlagCondition | SegmentCondition;
273273

274274
export type UserConditionComparisonValueTypeMap = {
275-
[UserComparator.IsOneOf]: Readonly<string[]>;
276-
[UserComparator.IsNotOneOf]: Readonly<string[]>;
277-
[UserComparator.ContainsAnyOf]: Readonly<string[]>;
278-
[UserComparator.NotContainsAnyOf]: Readonly<string[]>;
275+
[UserComparator.TextIsOneOf]: Readonly<string[]>;
276+
[UserComparator.TextIsNotOneOf]: Readonly<string[]>;
277+
[UserComparator.TextContainsAnyOf]: Readonly<string[]>;
278+
[UserComparator.TextNotContainsAnyOf]: Readonly<string[]>;
279279
[UserComparator.SemVerIsOneOf]: Readonly<string[]>;
280280
[UserComparator.SemVerIsNotOneOf]: Readonly<string[]>;
281281
[UserComparator.SemVerLess]: string;
@@ -288,8 +288,8 @@ export type UserConditionComparisonValueTypeMap = {
288288
[UserComparator.NumberLessOrEquals]: number;
289289
[UserComparator.NumberGreater]: number;
290290
[UserComparator.NumberGreaterOrEquals]: number;
291-
[UserComparator.SensitiveIsOneOf]: Readonly<string[]>;
292-
[UserComparator.SensitiveIsNotOneOf]: Readonly<string[]>;
291+
[UserComparator.SensitiveTextIsOneOf]: Readonly<string[]>;
292+
[UserComparator.SensitiveTextIsNotOneOf]: Readonly<string[]>;
293293
[UserComparator.DateTimeBefore]: number;
294294
[UserComparator.DateTimeAfter]: number;
295295
[UserComparator.SensitiveTextEquals]: string;

0 commit comments

Comments
 (0)