feat(core-shared): add withRateLimit wrapper and conformance enforcement
- Add withRateLimit(rateLimit, fn) in rate-limit/with-rate-limit.ts, attaching the RateLimited brand at DI bind time - Extend wireUseCase to accept optional rateLimit?: IRateLimit and compose withRateLimit innermost (before analytics/audit); propagate __rateLimited through analytics + audit inline wrappers - Extend withSpan and withCapture PROPAGATED_BRANDS to include __rateLimited so the outermost binding carries the brand - Extend assertFeatureConformance to require __rateLimited brand when manifest.useCases[name].rateLimit.length > 0; refactored into helper functions to stay within complexity thresholds - Add rateLimit?: IRateLimit to BindContext; default to NoopRateLimit in web-next bindAllProduction and bindAllDevSeed aggregators - Unit tests for withRateLimit brand attachment, factory passthrough, and composition; synthetic fixture tests for conformance errors Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -7,6 +7,7 @@ import {
|
||||
isCaptured,
|
||||
isAudited,
|
||||
isAnalyzed,
|
||||
isRateLimited,
|
||||
} from "@/conformance/brand-runtime";
|
||||
import type {
|
||||
ITracer,
|
||||
@@ -15,6 +16,7 @@ import type {
|
||||
} from "@/instrumentation/tracer.interface";
|
||||
import type { ILogger } from "@/instrumentation/logger.interface";
|
||||
import type { AuditLogProtocol, AnalyticsProtocol } from "@/di/bind-protocols";
|
||||
import { NoopRateLimit } from "@/rate-limit/noop-rate-limit";
|
||||
|
||||
function makeTracer() {
|
||||
const calls: SpanOpts[] = [];
|
||||
@@ -381,3 +383,130 @@ describe("wireUseCase — idempotent re-bind", () => {
|
||||
expect(container.get(sym)).toBe(second);
|
||||
});
|
||||
});
|
||||
|
||||
describe("wireUseCase — rate limit path", () => {
|
||||
it("attaches RateLimited brand when rateLimit is provided", () => {
|
||||
const { tracer } = makeTracer();
|
||||
const logger = makeLogger();
|
||||
const container = new Container();
|
||||
const sym = Symbol("test.double");
|
||||
const rateLimit = new NoopRateLimit();
|
||||
|
||||
const wired = wireUseCase({
|
||||
container,
|
||||
symbol: sym,
|
||||
factory: doubleFactory,
|
||||
deps: [],
|
||||
feature: "test",
|
||||
layer: "use-case",
|
||||
name: "double",
|
||||
tracer,
|
||||
logger,
|
||||
rateLimit,
|
||||
});
|
||||
|
||||
expect(isRateLimited(wired)).toBe(true);
|
||||
expect(isInstrumented(wired)).toBe(true);
|
||||
expect(isCaptured(wired)).toBe(true);
|
||||
});
|
||||
|
||||
it("does not attach RateLimited brand when rateLimit is absent", () => {
|
||||
const { tracer } = makeTracer();
|
||||
const logger = makeLogger();
|
||||
const container = new Container();
|
||||
const sym = Symbol("test.double");
|
||||
|
||||
const wired = wireUseCase({
|
||||
container,
|
||||
symbol: sym,
|
||||
factory: doubleFactory,
|
||||
deps: [],
|
||||
feature: "test",
|
||||
layer: "use-case",
|
||||
name: "double",
|
||||
tracer,
|
||||
logger,
|
||||
});
|
||||
|
||||
expect(isRateLimited(wired)).toBe(false);
|
||||
});
|
||||
|
||||
it("executes factory correctly when rateLimit is provided", async () => {
|
||||
const { tracer } = makeTracer();
|
||||
const logger = makeLogger();
|
||||
const container = new Container();
|
||||
const sym = Symbol("test.double");
|
||||
const rateLimit = new NoopRateLimit();
|
||||
|
||||
const wired = wireUseCase({
|
||||
container,
|
||||
symbol: sym,
|
||||
factory: doubleFactory,
|
||||
deps: [],
|
||||
feature: "test",
|
||||
layer: "use-case",
|
||||
name: "double",
|
||||
tracer,
|
||||
logger,
|
||||
rateLimit,
|
||||
});
|
||||
|
||||
await expect(wired(5)).resolves.toBe(10);
|
||||
});
|
||||
|
||||
it("attaches RateLimited + Analyzed brands when both rateLimit and analytics are provided", () => {
|
||||
const { tracer } = makeTracer();
|
||||
const logger = makeLogger();
|
||||
const container = new Container();
|
||||
const sym = Symbol("test.double");
|
||||
const rateLimit = new NoopRateLimit();
|
||||
const analytics = makeAnalytics();
|
||||
|
||||
const wired = wireUseCase({
|
||||
container,
|
||||
symbol: sym,
|
||||
factory: doubleFactory,
|
||||
deps: [],
|
||||
feature: "test",
|
||||
layer: "use-case",
|
||||
name: "double",
|
||||
tracer,
|
||||
logger,
|
||||
rateLimit,
|
||||
analytics,
|
||||
});
|
||||
|
||||
expect(isRateLimited(wired)).toBe(true);
|
||||
expect(isAnalyzed(wired)).toBe(true);
|
||||
expect(isInstrumented(wired)).toBe(true);
|
||||
expect(isCaptured(wired)).toBe(true);
|
||||
});
|
||||
|
||||
it("attaches RateLimited + Audited brands when both rateLimit and auditLog are provided", () => {
|
||||
const { tracer } = makeTracer();
|
||||
const logger = makeLogger();
|
||||
const container = new Container();
|
||||
const sym = Symbol("test.double");
|
||||
const rateLimit = new NoopRateLimit();
|
||||
const auditLog = makeAuditLog();
|
||||
|
||||
const wired = wireUseCase({
|
||||
container,
|
||||
symbol: sym,
|
||||
factory: doubleFactory,
|
||||
deps: [],
|
||||
feature: "test",
|
||||
layer: "use-case",
|
||||
name: "double",
|
||||
tracer,
|
||||
logger,
|
||||
rateLimit,
|
||||
auditLog,
|
||||
});
|
||||
|
||||
expect(isRateLimited(wired)).toBe(true);
|
||||
expect(isAudited(wired)).toBe(true);
|
||||
expect(isInstrumented(wired)).toBe(true);
|
||||
expect(isCaptured(wired)).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user