import { eq } from 'drizzle-orm' import { beforeEach, describe, expect, it } from 'vitest' import { buildServer } from '../../src/server.js' import { environments } from '../../src/db/schema.js' import { createAdminKey, createClientKey, createProject, createRootKey, } from '../helpers/fixtures.js' import { getTestDb, truncateAll } from '../helpers/db.js' const describeIfDb = process.env.TEST_DATABASE_URL ? describe : describe.skip describeIfDb('phase 1 integration', () => { const db = process.env.TEST_DATABASE_URL ? getTestDb() : undefined beforeEach(async () => { process.env.DATABASE_URL = process.env.TEST_DATABASE_URL await truncateAll(db!) }) it('supports the full happy path and environment-specific evaluation', async () => { const app = await buildServer({ db }) const rootKey = await createRootKey(db!) const project = await createProject(app, rootKey) const adminKey = await createAdminKey(app, rootKey, project.id) const envs = await db!.select().from(environments).where(eq(environments.projectId, project.id)) expect(envs.map((environment) => environment.slug).sort()).toEqual([ 'development', 'production', ]) await app.inject({ method: 'POST', url: `/api/v1/admin/projects/${project.id}/flags`, headers: { authorization: adminKey }, payload: { name: 'Checkout', key: 'checkout' }, }) await app.inject({ method: 'POST', url: `/api/v1/admin/projects/${project.id}/flags/checkout/environments/production/enable`, headers: { authorization: adminKey }, }) const dev = envs.find((environment) => environment.slug === 'development')! const production = envs.find((environment) => environment.slug === 'production')! const devClient = await createClientKey(app, adminKey, project.id, dev.id) const productionClient = await createClientKey(app, adminKey, project.id, production.id) const devResponse = await app.inject({ method: 'GET', url: '/api/v1/client/features/checkout', headers: { authorization: devClient }, }) const productionResponse = await app.inject({ method: 'GET', url: '/api/v1/client/features/checkout', headers: { authorization: productionClient }, }) expect(devResponse.json()).toMatchObject({ name: 'checkout', enabled: false }) expect(productionResponse.json()).toMatchObject({ name: 'checkout', enabled: true }) await app.close() }) it('evaluation always returns the environment default, ignoring query params', async () => { const app = await buildServer({ db }) const rootKey = await createRootKey(db!) const project = await createProject(app, rootKey) const adminKey = await createAdminKey(app, rootKey, project.id) const [firstEnv] = await db! .select() .from(environments) .where(eq(environments.projectId, project.id)) .limit(1) const clientKey = await createClientKey(app, adminKey, project.id, firstEnv.id) await app.inject({ method: 'POST', url: `/api/v1/admin/projects/${project.id}/flags`, headers: { authorization: adminKey }, payload: { name: 'Checkout', key: 'checkout' }, }) await app.inject({ method: 'POST', url: `/api/v1/admin/projects/${project.id}/flags/checkout/environments/${firstEnv.slug}/enable`, headers: { authorization: adminKey }, }) const withQuery = await app.inject({ method: 'GET', url: '/api/v1/client/features/checkout?userId=user_abc123', headers: { authorization: clientKey }, }) const withoutQuery = await app.inject({ method: 'GET', url: '/api/v1/client/features/checkout', headers: { authorization: clientKey }, }) expect(withQuery.json()).toMatchObject({ enabled: true, reason: 'default' }) expect(withoutQuery.json()).toMatchObject({ enabled: true, reason: 'default' }) await app.close() }) it('enforces auth scopes', async () => { const app = await buildServer({ db }) const rootKey = await createRootKey(db!) const project = await createProject(app, rootKey, 'a') const other = await createProject(app, rootKey, 'b') const adminKey = await createAdminKey(app, rootKey, project.id) const [environment] = await db! .select() .from(environments) .where(eq(environments.projectId, project.id)) .limit(1) const clientKey = await createClientKey(app, adminKey, project.id, environment.id) expect( ( await app.inject({ method: 'GET', url: `/api/v1/admin/projects/${project.id}/flags`, headers: { authorization: clientKey }, }) ).statusCode, ).toBe(403) expect( ( await app.inject({ method: 'GET', url: `/api/v1/admin/projects/${other.id}`, headers: { authorization: adminKey }, }) ).statusCode, ).toBe(403) expect( ( await app.inject({ method: 'POST', url: '/api/v1/admin/projects', headers: { authorization: adminKey }, payload: { name: 'Nope', slug: 'nope' }, }) ).statusCode, ).toBe(403) await app.close() }) it('returns conflicts for duplicate resources', async () => { const app = await buildServer({ db }) const rootKey = await createRootKey(db!) const project = await createProject(app, rootKey) const adminKey = await createAdminKey(app, rootKey, project.id) const duplicateProject = await app.inject({ method: 'POST', url: '/api/v1/admin/projects', headers: { authorization: rootKey }, payload: { name: 'Duplicate', slug: project.slug }, }) expect(duplicateProject.statusCode).toBe(409) await app.inject({ method: 'POST', url: `/api/v1/admin/projects/${project.id}/flags`, headers: { authorization: adminKey }, payload: { name: 'Checkout', key: 'checkout' }, }) const duplicateFlag = await app.inject({ method: 'POST', url: `/api/v1/admin/projects/${project.id}/flags`, headers: { authorization: adminKey }, payload: { name: 'Checkout 2', key: 'checkout' }, }) expect(duplicateFlag.statusCode).toBe(409) await app.close() }) it('cascades project deletion', async () => { const app = await buildServer({ db }) const rootKey = await createRootKey(db!) const project = await createProject(app, rootKey) const response = await app.inject({ method: 'DELETE', url: `/api/v1/admin/projects/${project.id}`, headers: { authorization: rootKey }, }) expect(response.statusCode).toBe(204) expect( await db!.select().from(environments).where(eq(environments.projectId, project.id)), ).toEqual([]) await app.close() }) })