Files
support_backend/tests/integration/knowledge-retrieval.test.ts
T
saqib mirandClaude Sonnet 5 40687f68fa feat(010-identity-auth): real staff login, session verification, and role gating
Replaces the no-op fastify.authenticate stub and the never-implemented
identity/auth login with real bcrypt password verification, JWT session
issuance/verification (reusing the existing JWT_SECRET), and a Redis-backed
revocation denylist for logout. Adds requireRole('ADMIN') to admin-only
configuration writes across 002-009 that previously relied on a decorator
that never actually checked anything. Adds self-identity (GET /auth/me,
re-validated against live account state) and admin-provisioned accounts
(POST /admin/users).

Making the auth check genuinely reject invalid/missing tokens exposed that
~18 pre-existing integration test files called already-gated routes with no
Authorization header (safe against the old no-op stub, broken against a real
one) — fixed via a shared tests/helpers/auth.ts (loginAs/authHeader) and a
file-by-file pass, plus two related SLA-run cleanup races exposed once admin
setup calls in those files' own beforeAll blocks started actually succeeding.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-07 12:45:37 +05:30

98 lines
3.5 KiB
TypeScript

import { describe, it, expect, beforeAll, afterAll } from 'vitest';
import { buildApp } from '@/app';
import { prismaClient } from '@/infrastructure/database';
import { FastifyInstance } from 'fastify';
import { loginAs, authHeader } from '../helpers/auth';
/** Covers specs/004-product-knowledge/quickstart.md Scenario 6 against a real Postgres. */
describe('Knowledge retrieval — scoping and ranking', () => {
let app: FastifyInstance;
let token: string;
const productAId = `TEST_RETRIEVE_A_${Date.now()}`;
const productBId = `TEST_RETRIEVE_B_${Date.now()}`;
const codes: string[] = [];
beforeAll(async () => {
app = await buildApp();
token = await loginAs(app, 'ADMIN');
await prismaClient.product.create({
data: { externalProductId: productAId, name: 'Product A', status: 'active' },
});
await prismaClient.product.create({
data: { externalProductId: productBId, name: 'Product B', status: 'active' },
});
});
afterAll(async () => {
await prismaClient.knowledgeEntry.deleteMany({ where: { code: { in: codes } } });
await prismaClient.product.deleteMany({
where: { externalProductId: { in: [productAId, productBId] } },
});
await app.close();
});
async function createAndPublish(externalProductId: string, code: string) {
codes.push(code);
await app.inject({
method: 'POST',
url: `/admin/products/${externalProductId}/knowledge`,
headers: authHeader(token),
payload: { code, type: 'faq', problem: `problem for ${code}` },
});
await app.inject({
method: 'PATCH',
url: `/admin/knowledge/${code}/publish`,
headers: authHeader(token),
});
}
it("never returns another product's entries", async () => {
const codeA = `KB-A-${Date.now()}`;
const codeB = `KB-B-${Date.now()}`;
await createAndPublish(productAId, codeA);
await createAndPublish(productBId, codeB);
const response = await app.inject({
method: 'GET',
url: `/knowledge/retrieve?productId=${productAId}`,
});
const returnedCodes = response.json().data.map((e: { code: string }) => e.code);
expect(returnedCodes).toContain(codeA);
expect(returnedCodes).not.toContain(codeB);
});
it('ranks a validated entry ahead of an equally-matching unvalidated one', async () => {
const validatedCode = `KB-VALID-${Date.now()}`;
const unvalidatedCode = `KB-UNVALID-${Date.now()}`;
await createAndPublish(productAId, validatedCode);
await createAndPublish(productAId, unvalidatedCode);
await app.inject({
method: 'PATCH',
url: `/admin/knowledge/${validatedCode}/validate`,
headers: authHeader(token),
payload: { validationStatus: 'validated' },
});
const response = await app.inject({
method: 'GET',
url: `/knowledge/retrieve?productId=${productAId}`,
});
const returnedCodes = response.json().data.map((e: { code: string }) => e.code);
const validatedIndex = returnedCodes.indexOf(validatedCode);
const unvalidatedIndex = returnedCodes.indexOf(unvalidatedCode);
expect(validatedIndex).toBeGreaterThanOrEqual(0);
expect(unvalidatedIndex).toBeGreaterThanOrEqual(0);
expect(validatedIndex).toBeLessThan(unvalidatedIndex);
});
it('returns an empty array, never an error, when nothing matches', async () => {
const response = await app.inject({
method: 'GET',
url: `/knowledge/retrieve?productId=TEST_NEVER_REGISTERED_${Date.now()}`,
});
expect(response.statusCode).toBe(200);
expect(response.json().data).toEqual([]);
});
});