Publication IA: - 4 templates (magazine, brief, essay, simple) avec CSS riche - Rewrite IA (article/exercises/tutorial/reference/mixed) - Modération avec timeout 12s + fallback safe - Quotas publish_enhance par tier (basic=2, pro=15, business=100) - Détection contenu stale (hash) - Migration DB publishedContent/publishedTemplate/publishedSourceHash Fixes: - cheerio v1.2: Element -> AnyNode (domhandler), decodeEntities cast - _isShared ajouté au type Note (champ virtuel serveur) - callout colors PDF export: extraction fonction pure testable - admin/published: guard note.userId null - Cmd+S fonctionne en mode dialog (pas seulement fullPage) i18n: - 23 clés publish* traduites dans les 15 locales - Extension Web Clipper: 13 locales mise à jour Tests: - callout-colors.test.ts (6 tests) - note-visible-in-view.test.ts (5 tests) - entitlements.test.ts + byok-entitlements.test.ts: mock usageLog + unstubAllEnvs - 199/199 tests passent Tracker: user-stories.md sync avec sprint-status.yaml
56 lines
1.6 KiB
TypeScript
56 lines
1.6 KiB
TypeScript
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
import { canUseFeature } from '@/lib/entitlements';
|
|
|
|
vi.mock('@/lib/redis', () => ({
|
|
redis: {
|
|
get: vi.fn(),
|
|
mget: vi.fn(),
|
|
eval: vi.fn(),
|
|
},
|
|
}));
|
|
|
|
vi.mock('@/lib/prisma', () => ({
|
|
prisma: {
|
|
subscription: { findUnique: vi.fn() },
|
|
userAPIKey: { count: vi.fn() },
|
|
planEntitlement: { findMany: vi.fn().mockResolvedValue([]) },
|
|
usageLog: { findMany: vi.fn().mockResolvedValue([]) },
|
|
},
|
|
}));
|
|
|
|
import { redis } from '@/lib/redis';
|
|
import { prisma } from '@/lib/prisma';
|
|
|
|
describe('BYOK entitlements', () => {
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
vi.mocked(prisma.subscription.findUnique).mockResolvedValue({
|
|
userId: 'user1',
|
|
tier: 'BASIC',
|
|
status: 'ACTIVE',
|
|
currentPeriodStart: new Date(),
|
|
currentPeriodEnd: new Date(),
|
|
} as never);
|
|
});
|
|
|
|
it('returns byokConfigured=true when BYOK active but quota exhausted (bypass is per-provider in routes)', async () => {
|
|
vi.mocked(redis.get).mockResolvedValue('30');
|
|
vi.mocked(prisma.userAPIKey.count).mockResolvedValue(1);
|
|
|
|
const result = await canUseFeature('user1', 'semantic_search');
|
|
|
|
expect(result.allowed).toBe(false);
|
|
expect(result.byokConfigured).toBe(true);
|
|
});
|
|
|
|
it('denies when quota exhausted and no BYOK', async () => {
|
|
vi.mocked(redis.get).mockResolvedValue('30');
|
|
vi.mocked(prisma.userAPIKey.count).mockResolvedValue(0);
|
|
|
|
const result = await canUseFeature('user1', 'semantic_search');
|
|
|
|
expect(result.allowed).toBe(false);
|
|
expect(result.byokConfigured).toBe(false);
|
|
});
|
|
});
|