refactor(pkgs): share vitest config and dedupe helpers (#3384)

This commit is contained in:
Hampus authored and GitHub committed 2026-10-10 23:23:34 +02:00
1 parent 5695510980
commit 575a5adbef
43 files changed
+172 -874

No files matched your search

+1 -16
View File
@@ -1,18 +1,3 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import {defineConfig} from 'vitest/config';
export default defineConfig({
resolve: {tsconfigPaths: true},
test: {
globals: true,
environment: 'node',
include: ['**/*.{test,spec}.{ts,tsx}'],
exclude: ['node_modules', 'dist'],
coverage: {
provider: 'v8',
reporter: ['text', 'json', 'html'],
exclude: ['**/*.test.tsx', '**/*.spec.tsx', 'node_modules/'],
},
},
});
export {default} from '../../tsconfigs/vitest.base.mts';
-1
View File
@@ -13,7 +13,6 @@
},
"dependencies": {
"@fluxer/constants": "workspace:*",
"@fluxer/hono_types": "workspace:*",
"@fluxer/i18n": "workspace:*",
"@fluxer/logger": "workspace:*",
"@fluxer/schema": "workspace:*",
@@ -1,34 +1,29 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import {APIErrorCodes} from '@fluxer/constants/src/ApiErrorCodes';
import {BadRequestError} from '@fluxer/errors/src/domains/core/BadRequestError';
import {AppErrorHandler} from '@fluxer/errors/src/domains/core/ErrorHandlers';
import {AppErrorHandler, type BaseHonoEnv} from '@fluxer/errors/src/domains/core/ErrorHandlers';
import {ServiceUnavailableError} from '@fluxer/errors/src/HttpErrors';
import type {BaseHonoEnv} from '@fluxer/hono_types/src/HonoTypes';
import {Hono} from 'hono';
import {beforeEach, describe, expect, it, vi} from 'vitest';
type LogCall = [Record<string, unknown>, string | undefined];
const logCalls = vi.hoisted(() => ({
debug: [] as Array<LogCall>,
warn: [] as Array<LogCall>,
error: [] as Array<LogCall>,
}));
vi.mock('@fluxer/logger/src/Logger', async (importOriginal) => {
const actual = await importOriginal<typeof import('@fluxer/logger/src/Logger')>();
const record = (bucket: Array<LogCall>) => (obj: Record<string, unknown>, msg?: string) => {
bucket.push([obj, msg]);
};
return {
...actual,
createLogger: () => ({
trace: () => {},
debug: record(logCalls.debug),
debug: () => {},
info: () => {},
warn: record(logCalls.warn),
error: record(logCalls.error),
warn: () => {},
error: (obj: Record<string, unknown>, msg?: string) => {
logCalls.error.push([obj, msg]);
},
fatal: () => {},
}),
};
@@ -42,8 +37,6 @@ function createApp(): Hono<BaseHonoEnv> {
describe('AppErrorHandler logging', () => {
beforeEach(() => {
logCalls.debug.length = 0;
logCalls.warn.length = 0;
logCalls.error.length = 0;
});
@@ -75,20 +68,6 @@ describe('AppErrorHandler logging', () => {
expect(loggedError.cause).toBe(cause);
});
it('logs 4xx FluxerErrors at debug rather than error', async () => {
const app = createApp();
app.get('/thing', () => {
throw new BadRequestError({code: APIErrorCodes.BAD_REQUEST});
});
const response = await app.request('/thing');
expect(response.status).toBe(400);
expect(logCalls.error).toHaveLength(0);
expect(logCalls.debug).toHaveLength(1);
const [details, message] = logCalls.debug[0]!;
expect(message).toBe('Request rejected');
expect(details.status).toBe(400);
});
it('logs the matched route pattern instead of the request path', async () => {
const app = createApp();
app.get('/reset/:token', () => {
@@ -99,15 +78,4 @@ describe('AppErrorHandler logging', () => {
expect(logCalls.error).toHaveLength(1);
expect(logCalls.error[0]![0].path).toBe('/reset/:token');
});
it('still reports unexpected errors as unhandled', async () => {
const app = createApp();
app.get('/thing', () => {
throw new Error('boom');
});
const response = await app.request('/thing');
expect(response.status).toBe(500);
expect(logCalls.error).toHaveLength(1);
expect(logCalls.error[0]![1]).toBe('Unhandled error occurred');
});
});
@@ -3,9 +3,8 @@
import {APIErrorCodes} from '@fluxer/constants/src/ApiErrorCodes';
import {Locales} from '@fluxer/constants/src/Locales';
import {BadRequestError} from '@fluxer/errors/src/domains/core/BadRequestError';
import {AppErrorHandler} from '@fluxer/errors/src/domains/core/ErrorHandlers';
import {AppErrorHandler, type BaseHonoEnv} from '@fluxer/errors/src/domains/core/ErrorHandlers';
import {getErrorMessageUnsafe} from '@fluxer/errors/src/i18n/ErrorI18n';
import type {BaseHonoEnv} from '@fluxer/hono_types/src/HonoTypes';
import {Logger} from '@fluxer/logger/src/Logger';
import {Hono} from 'hono';
import {afterEach, describe, expect, it, vi} from 'vitest';
@@ -20,11 +20,27 @@ import {FluxerError} from '@fluxer/errors/src/FluxerError';
import {ErrorCodeToI18nKey} from '@fluxer/errors/src/i18n/ErrorCodeMappings';
import {getErrorMessageUnsafe} from '@fluxer/errors/src/i18n/ErrorI18n';
import type {ErrorI18nKey} from '@fluxer/errors/src/i18n/ErrorI18nMessages';
import type {BaseHonoEnv, ErrorI18nService} from '@fluxer/hono_types/src/HonoTypes';
import {createLogger} from '@fluxer/logger/src/Logger';
import type {Context} from 'hono';
import {HTTPException} from 'hono/http-exception';
interface ErrorI18nService {
getMessage(
key: string,
locale: string | null | undefined,
variables?: Record<string, unknown>,
fallbackMessage?: string,
): string;
}
export interface BaseHonoEnv {
Variables: {
errorI18nService?: ErrorI18nService;
requestLocale?: string;
requestId?: string;
};
}
const logger = createLogger('errors');
const LOCALE_LOOKUP = new Map<string, string>(Object.values(Locales).map((locale) => [locale.toLowerCase(), locale]));
+1 -16
View File
@@ -1,18 +1,3 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import {defineConfig} from 'vitest/config';
export default defineConfig({
resolve: {tsconfigPaths: true},
test: {
globals: true,
environment: 'node',
include: ['**/*.{test,spec}.{ts,tsx}'],
exclude: ['node_modules', 'dist'],
coverage: {
provider: 'v8',
reporter: ['text', 'json', 'html'],
exclude: ['**/*.test.tsx', '**/*.spec.tsx', 'node_modules/'],
},
},
});
export {default} from '../../tsconfigs/vitest.base.mts';
@@ -1,27 +0,0 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
const ASCII_UPPER_A = 65;
const ASCII_UPPER_Z = 90;
const REGION_CODE_LENGTH = 2;
function isAsciiUpperAlpha2(value: string): boolean {
return (
value.length === REGION_CODE_LENGTH &&
value.charCodeAt(0) >= ASCII_UPPER_A &&
value.charCodeAt(0) <= ASCII_UPPER_Z &&
value.charCodeAt(1) >= ASCII_UPPER_A &&
value.charCodeAt(1) <= ASCII_UPPER_Z
);
}
export function normalizeRegionCode(regionCode: string): string | undefined {
const trimmedRegionCode = regionCode.trim();
if (trimmedRegionCode.length !== REGION_CODE_LENGTH) {
return undefined;
}
const upperRegionCode = trimmedRegionCode.toUpperCase();
if (!isAsciiUpperAlpha2(upperRegionCode)) {
return undefined;
}
return upperRegionCode;
}
@@ -1,46 +0,0 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import {Locales} from '@fluxer/constants/src/Locales';
import {normalizeRegionCode} from '@fluxer/geo_utils/src/RegionCodeValidation';
const DISPLAY_NAME_TYPE: Intl.DisplayNamesOptions['type'] = 'region';
const DISPLAY_NAME_FALLBACK: Intl.DisplayNamesOptions['fallback'] = 'none';
const displayNamesByLocale = new Map<string, Intl.DisplayNames>();
function resolveLocale(locale?: string): string {
const trimmedLocale = locale?.trim();
if (trimmedLocale && trimmedLocale.length > 0) {
return trimmedLocale;
}
return Locales.EN_US;
}
function getDisplayNames(locale?: string): Intl.DisplayNames {
const localeCode = resolveLocale(locale);
const cachedDisplayNames = displayNamesByLocale.get(localeCode);
if (cachedDisplayNames) {
return cachedDisplayNames;
}
const displayNames = new Intl.DisplayNames([localeCode], {
type: DISPLAY_NAME_TYPE,
fallback: DISPLAY_NAME_FALLBACK,
});
displayNamesByLocale.set(localeCode, displayNames);
return displayNames;
}
function resolveRegionDisplayNameFromFormatter(
regionCode: string,
displayNames: Intl.DisplayNames,
): string | undefined {
const normalizedRegionCode = normalizeRegionCode(regionCode);
if (!normalizedRegionCode) {
return undefined;
}
return displayNames.of(normalizedRegionCode) ?? undefined;
}
export function resolveRegionDisplayName(regionCode: string, locale?: string): string | undefined {
const displayNames = getDisplayNames(locale);
return resolveRegionDisplayNameFromFormatter(regionCode, displayNames);
}
+19 -24
View File
@@ -1,33 +1,28 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import {normalizeRegionCode} from '@fluxer/geo_utils/src/RegionCodeValidation';
import {resolveRegionDisplayName} from '@fluxer/geo_utils/src/RegionDisplayNameResolver';
import {Locales} from '@fluxer/constants/src/Locales';
interface RegionDisplayNameOptions {
locale?: string;
fallbackToRegionCode?: boolean;
const displayNamesByLocale = new Map<string, Intl.DisplayNames>();
function getDisplayNames(locale?: string): Intl.DisplayNames {
const localeCode = locale?.trim() || Locales.EN_US;
let displayNames = displayNamesByLocale.get(localeCode);
if (!displayNames) {
displayNames = new Intl.DisplayNames([localeCode], {type: 'region', fallback: 'none'});
displayNamesByLocale.set(localeCode, displayNames);
}
return displayNames;
}
function applyRegionCodeFallback(
regionCode: string,
displayName: string | undefined,
options?: RegionDisplayNameOptions,
): string | undefined {
if (displayName) {
return displayName;
}
if (!options?.fallbackToRegionCode) {
export function getRegionDisplayName(regionCode: string, options?: {locale?: string}): string | undefined {
const displayNames = getDisplayNames(options?.locale);
const trimmedRegionCode = regionCode.trim();
if (trimmedRegionCode.length !== 2) {
return undefined;
}
const normalizedRegionCode = normalizeRegionCode(regionCode);
if (normalizedRegionCode) {
return normalizedRegionCode;
const upperRegionCode = trimmedRegionCode.toUpperCase();
if (!/^[A-Z]{2}$/.test(upperRegionCode)) {
return undefined;
}
const trimmedRegionCode = regionCode.trim();
return trimmedRegionCode.length > 0 ? trimmedRegionCode : undefined;
}
export function getRegionDisplayName(regionCode: string, options?: RegionDisplayNameOptions): string | undefined {
const displayName = resolveRegionDisplayName(regionCode, options?.locale);
return applyRegionCodeFallback(regionCode, displayName, options);
return displayNames.of(upperRegionCode) || undefined;
}
-12
View File
@@ -1,12 +0,0 @@
{
"name": "@fluxer/hono_types",
"version": "0.0.0",
"private": true,
"type": "module",
"scripts": {
"typecheck": "tsc --noEmit"
},
"devDependencies": {
"typescript": "catalog:ts7"
}
}
-18
View File
@@ -1,18 +0,0 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
export interface ErrorI18nService {
getMessage(
key: string,
locale: string | null | undefined,
variables?: Record<string, unknown>,
fallbackMessage?: string,
): string;
}
export interface BaseHonoEnv {
Variables: {
errorI18nService?: ErrorI18nService;
requestLocale?: string;
requestId?: string;
};
}
-7
View File
@@ -1,7 +0,0 @@
{
"extends": "../../tsconfigs/package.json",
"compilerOptions": {
"types": []
},
"include": ["src/**/*"]
}
+1 -16
View File
@@ -1,18 +1,3 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import {defineConfig} from 'vitest/config';
export default defineConfig({
resolve: {tsconfigPaths: true},
test: {
globals: true,
environment: 'node',
include: ['**/*.{test,spec}.{ts,tsx}'],
exclude: ['node_modules', 'dist'],
coverage: {
provider: 'v8',
reporter: ['text', 'json', 'html'],
exclude: ['**/*.test.tsx', '**/*.spec.tsx', 'node_modules/'],
},
},
});
export {default} from '../../tsconfigs/vitest.base.mts';
+1 -16
View File
@@ -1,18 +1,3 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import {defineConfig} from 'vitest/config';
export default defineConfig({
resolve: {tsconfigPaths: true},
test: {
globals: true,
environment: 'node',
include: ['**/*.{test,spec}.{ts,tsx}'],
exclude: ['node_modules', 'dist'],
coverage: {
provider: 'v8',
reporter: ['text', 'json', 'html'],
exclude: ['**/*.test.tsx', '**/*.spec.tsx', 'node_modules/'],
},
},
});
export {default} from '../../tsconfigs/vitest.base.mts';
-18
View File
@@ -1,18 +0,0 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import type {LimitKey} from '@fluxer/constants/src/LimitConfigMetadata';
import type {LimitConfigSnapshot, LimitConfigWireFormat} from '@fluxer/limits/src/LimitTypes';
export interface LimitConfigCodecOptions {
defaults?: Record<LimitKey, number>;
defaultsHash?: string;
}
export interface ILimitConfigCodec {
computeOverrides(
fullLimits: Partial<Record<LimitKey, number>>,
defaults: Record<LimitKey, number>,
): Partial<Record<LimitKey, number>>;
toWireFormat(config: LimitConfigSnapshot, options?: LimitConfigCodecOptions): LimitConfigWireFormat;
fromWireFormat(wireFormat: LimitConfigWireFormat, options?: LimitConfigCodecOptions): LimitConfigSnapshot;
}
-9
View File
@@ -1,9 +0,0 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import type {LimitKey} from '@fluxer/constants/src/LimitConfigMetadata';
import type {LimitEvaluationOptions, LimitEvaluationResult, LimitMatchContext} from '@fluxer/limits/src/LimitTypes';
export interface ILimitEvaluator {
resolveAll(ctx: LimitMatchContext, options?: LimitEvaluationOptions): LimitEvaluationResult;
resolveOne(ctx: LimitMatchContext, key: LimitKey, options?: LimitEvaluationOptions): number;
}
-88
View File
@@ -1,88 +0,0 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import {LIMIT_KEYS, type LimitKey} from '@fluxer/constants/src/LimitConfigMetadata';
import type {ILimitConfigCodec, LimitConfigCodecOptions} from '@fluxer/limits/src/ILimitConfigCodec';
import {DEFAULT_FREE_LIMITS} from '@fluxer/limits/src/LimitDefaults';
import {computeDefaultsHash} from '@fluxer/limits/src/LimitHashing';
import type {LimitConfigSnapshot, LimitConfigWireFormat} from '@fluxer/limits/src/LimitTypes';
const WIRE_COMPATIBILITY_LIMIT_KEYS: ReadonlySet<LimitKey> = new Set<LimitKey>([
'max_guild_emojis',
'max_guild_emojis_animated_more',
'max_guild_emojis_animated',
'max_guild_emojis_static_more',
'max_guild_emojis_static',
'max_guild_stickers_more',
'max_guild_stickers',
]);
export class LimitConfigCodec implements ILimitConfigCodec {
computeOverrides(
fullLimits: Partial<Record<LimitKey, number>>,
defaults: Record<LimitKey, number>,
): Partial<Record<LimitKey, number>> {
const overrides: Partial<Record<LimitKey, number>> = {};
for (const key of LIMIT_KEYS) {
const value = fullLimits[key];
if (value === undefined) {
continue;
}
if (value !== defaults[key]) {
overrides[key] = value;
}
}
return overrides;
}
private addWireCompatibilityOverrides(
overrides: Partial<Record<LimitKey, number>>,
fullLimits: Partial<Record<LimitKey, number>>,
): Partial<Record<LimitKey, number>> {
for (const key of WIRE_COMPATIBILITY_LIMIT_KEYS) {
const value = fullLimits[key];
if (value !== undefined) {
overrides[key] = value;
}
}
return overrides;
}
toWireFormat(config: LimitConfigSnapshot, options?: LimitConfigCodecOptions): LimitConfigWireFormat {
const defaults = options?.defaults ?? DEFAULT_FREE_LIMITS;
const defaultsHash = options?.defaultsHash ?? computeDefaultsHash();
const rules = config.rules.map((rule) => {
const overrides = this.addWireCompatibilityOverrides(this.computeOverrides(rule.limits, defaults), rule.limits);
return {
id: rule.id,
filters: rule.filters,
overrides,
};
});
return {
version: 2,
traitDefinitions: config.traitDefinitions,
rules,
defaultsHash,
};
}
fromWireFormat(wireFormat: LimitConfigWireFormat, options?: LimitConfigCodecOptions): LimitConfigSnapshot {
const defaults = options?.defaults ?? DEFAULT_FREE_LIMITS;
const rules = wireFormat.rules.map((rule) => {
const limits: Partial<Record<LimitKey, number>> = {
...defaults,
...rule.overrides,
};
return {
id: rule.id,
filters: rule.filters,
limits,
};
});
return {
version: wireFormat.version,
traitDefinitions: wireFormat.traitDefinitions,
rules,
};
}
}
+60 -6
View File
@@ -1,22 +1,76 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import type {LimitKey} from '@fluxer/constants/src/LimitConfigMetadata';
import {LimitConfigCodec} from '@fluxer/limits/src/LimitConfigCodec';
import {LIMIT_KEYS, type LimitKey} from '@fluxer/constants/src/LimitConfigMetadata';
import {DEFAULT_FREE_LIMITS} from '@fluxer/limits/src/LimitDefaults';
import {computeDefaultsHash} from '@fluxer/limits/src/LimitHashing';
import type {LimitConfigSnapshot, LimitConfigWireFormat} from '@fluxer/limits/src/LimitTypes';
const defaultLimitConfigCodec = new LimitConfigCodec();
const WIRE_COMPATIBILITY_LIMIT_KEYS: ReadonlySet<LimitKey> = new Set<LimitKey>([
'max_guild_emojis',
'max_guild_emojis_animated_more',
'max_guild_emojis_animated',
'max_guild_emojis_static_more',
'max_guild_emojis_static',
'max_guild_stickers_more',
'max_guild_stickers',
]);
export function computeOverrides(
fullLimits: Partial<Record<LimitKey, number>>,
defaults: Record<LimitKey, number>,
): Partial<Record<LimitKey, number>> {
return defaultLimitConfigCodec.computeOverrides(fullLimits, defaults);
const overrides: Partial<Record<LimitKey, number>> = {};
for (const key of LIMIT_KEYS) {
const value = fullLimits[key];
if (value === undefined) {
continue;
}
if (value !== defaults[key]) {
overrides[key] = value;
}
}
return overrides;
}
export function computeWireFormat(config: LimitConfigSnapshot): LimitConfigWireFormat {
return defaultLimitConfigCodec.toWireFormat(config);
const defaultsHash = computeDefaultsHash();
const rules = config.rules.map((rule) => {
const overrides = computeOverrides(rule.limits, DEFAULT_FREE_LIMITS);
for (const key of WIRE_COMPATIBILITY_LIMIT_KEYS) {
const value = rule.limits[key];
if (value !== undefined) {
overrides[key] = value;
}
}
return {
id: rule.id,
filters: rule.filters,
overrides,
};
});
return {
version: 2,
traitDefinitions: config.traitDefinitions,
rules,
defaultsHash,
};
}
export function expandWireFormat(wireFormat: LimitConfigWireFormat): LimitConfigSnapshot {
return defaultLimitConfigCodec.fromWireFormat(wireFormat);
const rules = wireFormat.rules.map((rule) => {
const limits: Partial<Record<LimitKey, number>> = {
...DEFAULT_FREE_LIMITS,
...rule.overrides,
};
return {
id: rule.id,
filters: rule.filters,
limits,
};
});
return {
version: wireFormat.version,
traitDefinitions: wireFormat.traitDefinitions,
rules,
};
}
-40
View File
@@ -1,40 +0,0 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import type {LimitKey} from '@fluxer/constants/src/LimitConfigMetadata';
import type {ILimitEvaluator} from '@fluxer/limits/src/ILimitEvaluator';
import {DEFAULT_FREE_LIMITS} from '@fluxer/limits/src/LimitDefaults';
import {applyRuleToResolvedLimits, ruleMatches, sortRulesBySpecificity} from '@fluxer/limits/src/LimitRuleRuntime';
import type {
LimitConfigSnapshot,
LimitEvaluationOptions,
LimitEvaluationResult,
LimitMatchContext,
LimitRule,
} from '@fluxer/limits/src/LimitTypes';
export class LimitEvaluator implements ILimitEvaluator {
private readonly sortedRules: Array<LimitRule>;
constructor(snapshot: LimitConfigSnapshot) {
this.sortedRules = sortRulesBySpecificity(snapshot.rules);
}
resolveAll(ctx: LimitMatchContext, options?: LimitEvaluationOptions): LimitEvaluationResult {
const evaluationContext = options?.evaluationContext ?? 'user';
const baseLimits = options?.baseLimits ?? DEFAULT_FREE_LIMITS;
const resolvedLimits = {...baseLimits};
for (const rule of this.sortedRules) {
if (!ruleMatches(rule.filters, ctx)) {
continue;
}
applyRuleToResolvedLimits(resolvedLimits, rule, evaluationContext);
}
return {
limits: resolvedLimits,
};
}
resolveOne(ctx: LimitMatchContext, key: LimitKey, options?: LimitEvaluationOptions): number {
return this.resolveAll(ctx, options).limits[key];
}
}
+11 -10
View File
@@ -1,8 +1,8 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import type {LimitKey} from '@fluxer/constants/src/LimitConfigMetadata';
import type {ILimitEvaluator} from '@fluxer/limits/src/ILimitEvaluator';
import {LimitEvaluator} from '@fluxer/limits/src/LimitEvaluator';
import {DEFAULT_FREE_LIMITS} from '@fluxer/limits/src/LimitDefaults';
import {applyRuleToResolvedLimits, ruleMatches, sortRulesBySpecificity} from '@fluxer/limits/src/LimitRuleRuntime';
import type {
LimitConfigSnapshot,
LimitEvaluationOptions,
@@ -10,17 +10,19 @@ import type {
LimitMatchContext,
} from '@fluxer/limits/src/LimitTypes';
export function createLimitEvaluator(snapshot: LimitConfigSnapshot): ILimitEvaluator {
return new LimitEvaluator(snapshot);
}
export function resolveLimits(
snapshot: LimitConfigSnapshot,
ctx: LimitMatchContext,
options?: LimitEvaluationOptions,
): LimitEvaluationResult {
const evaluator = createLimitEvaluator(snapshot);
return evaluator.resolveAll(ctx, options);
const evaluationContext = options?.evaluationContext ?? 'user';
const resolvedLimits = {...(options?.baseLimits ?? DEFAULT_FREE_LIMITS)};
for (const rule of sortRulesBySpecificity(snapshot.rules)) {
if (ruleMatches(rule.filters, ctx)) {
applyRuleToResolvedLimits(resolvedLimits, rule, evaluationContext);
}
}
return {limits: resolvedLimits};
}
export function resolveLimit(
@@ -29,6 +31,5 @@ export function resolveLimit(
key: LimitKey,
options?: LimitEvaluationOptions,
): number {
const evaluator = createLimitEvaluator(snapshot);
return evaluator.resolveOne(ctx, key, options);
return resolveLimits(snapshot, ctx, options).limits[key];
}
+1 -16
View File
@@ -1,18 +1,3 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import {defineConfig} from 'vitest/config';
export default defineConfig({
resolve: {tsconfigPaths: true},
test: {
globals: true,
environment: 'node',
include: ['**/*.{test,spec}.{ts,tsx}'],
exclude: ['node_modules', 'dist'],
coverage: {
provider: 'v8',
reporter: ['text', 'json', 'html'],
exclude: ['**/*.test.tsx', '**/*.spec.tsx', 'node_modules/'],
},
},
});
export {default} from '../../tsconfigs/vitest.base.mts';
@@ -3,50 +3,10 @@
import {
ExperimentRolloutCountryCodesSchema,
type ExperimentTargeting,
experimentBucket,
experimentRolloutCountryIncludes,
} from '@fluxer/schema/src/domains/experiment/ExperimentBucket';
import {describe, expect, test} from 'vitest';
const TARGETED_USER_ID = '1000000000000000001';
const OTHER_USER_ID = '1000000000000000002';
function syntheticUserIds(count: number): Array<string> {
const ids: Array<string> = [];
for (let index = 0; index < count; index++) {
ids.push((1400000000000000000n + BigInt(index)).toString());
}
return ids;
}
describe('experimentBucket', () => {
test('stays inside the basis point range for every synthetic id', () => {
for (const userId of syntheticUserIds(500)) {
const bucket = experimentBucket(userId, 'voice-ns-v1');
expect(Number.isInteger(bucket)).toBe(true);
expect(bucket).toBeGreaterThanOrEqual(0);
expect(bucket).toBeLessThan(10000);
}
});
test.each([
{userId: TARGETED_USER_ID, salt: 'voice-ns-v1', expected: 8241},
{userId: TARGETED_USER_ID, salt: 'voice-ns-v2', expected: 8500},
{userId: OTHER_USER_ID, salt: 'voice-ns-v1', expected: 5384},
{userId: OTHER_USER_ID, salt: 'voice-ns-v2', expected: 1357},
])('preserves the assignment bucket for $userId with salt $salt', ({userId, salt, expected}) => {
expect(experimentBucket(userId, salt)).toBe(expected);
});
test('changes with the salt for at least most user ids', () => {
const userIds = syntheticUserIds(200);
const changed = userIds.filter(
(userId) => experimentBucket(userId, 'voice-ns-v1') !== experimentBucket(userId, 'voice-ns-v2'),
);
expect(changed.length).toBeGreaterThan(userIds.length - 5);
});
});
describe('ExperimentRolloutCountryCodesSchema', () => {
test('accepts unique uppercase alpha-2 codes', () => {
expect(ExperimentRolloutCountryCodesSchema.parse(['SE', 'BR'])).toEqual(['SE', 'BR']);
+1 -16
View File
@@ -1,18 +1,3 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import {defineConfig} from 'vitest/config';
export default defineConfig({
resolve: {tsconfigPaths: true},
test: {
globals: true,
environment: 'node',
include: ['**/*.{test,spec}.{ts,tsx}'],
exclude: ['node_modules', 'dist'],
coverage: {
provider: 'v8',
reporter: ['text', 'json', 'html'],
exclude: ['**/*.test.tsx', '**/*.spec.tsx', 'node_modules/'],
},
},
});
export {default} from '../../tsconfigs/vitest.base.mts';
+7 -12
View File
@@ -16,7 +16,11 @@ function toClampedTimestamp(timestamp: number): number {
}
export function extractTimestamp(snowflake: string): number {
return extractTimestampFromSnowflake(snowflake);
try {
return extractTimestampWithEpoch(BigInt(snowflake), FLUXER_EPOCH);
} catch (_error) {
return Number.NaN;
}
}
export function extractTimestampBigInt(snowflake: bigint): number {
@@ -86,17 +90,8 @@ export function age(snowflake: string): number {
return Date.now() - timestamp;
}
export function extractTimestampFromSnowflake(snowflake: string, epoch?: string | bigint): number {
try {
const epochBigInt = epoch != null ? (typeof epoch === 'string' ? BigInt(epoch) : epoch) : FLUXER_EPOCH;
return extractTimestampWithEpoch(BigInt(snowflake), epochBigInt);
} catch (_error) {
return Number.NaN;
}
}
export function extractTimestampFromSnowflakeAsDate(snowflake: string, epoch?: string | bigint): Date {
const timestamp = extractTimestampFromSnowflake(snowflake, epoch);
export function extractTimestampFromSnowflakeAsDate(snowflake: string): Date {
const timestamp = extractTimestamp(snowflake);
if (Number.isNaN(timestamp)) {
return new Date();
}
+1 -16
View File
@@ -1,18 +1,3 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import {defineConfig} from 'vitest/config';
export default defineConfig({
resolve: {tsconfigPaths: true},
test: {
globals: true,
environment: 'node',
include: ['**/*.{test,spec}.{ts,tsx}'],
exclude: ['node_modules', 'dist'],
coverage: {
provider: 'v8',
reporter: ['text', 'json', 'html'],
exclude: ['**/*.test.tsx', '**/*.spec.tsx', 'node_modules/'],
},
},
});
export {default} from '../../tsconfigs/vitest.base.mts';