This commit is contained in:
@@ -0,0 +1,292 @@
|
||||
import { createHash, randomUUID } from 'node:crypto'
|
||||
import {
|
||||
applyRepositoryProfileServerMetadata,
|
||||
type RepositoryProfile,
|
||||
} from '@devrunbook/repository-intel'
|
||||
import { afterAll, beforeAll, describe, expect, it } from 'vitest'
|
||||
|
||||
import { closeDatabase, getSqlClient } from '../index'
|
||||
import { DrizzleGeneratedRunStore } from './generated-run-store'
|
||||
|
||||
const databaseIntegration =
|
||||
process.env.DEVRUNBOOK_DATABASE_INTEGRATION === 'true' &&
|
||||
Boolean(process.env.DATABASE_URL)
|
||||
|
||||
function profile(name: string): RepositoryProfile {
|
||||
return applyRepositoryProfileServerMetadata(
|
||||
{
|
||||
apiVersion: 'devrunbook.io/v1alpha1',
|
||||
kind: 'RepositoryProfile',
|
||||
metadata: { name, revision: 1, source: 'manual' },
|
||||
spec: {
|
||||
repositoryType: 'single-app',
|
||||
stack: {
|
||||
languages: ['TypeScript'],
|
||||
frameworks: [],
|
||||
packageManagers: ['pnpm'],
|
||||
databases: ['PostgreSQL'],
|
||||
deploymentTypes: ['Docker'],
|
||||
testFrameworks: ['Vitest'],
|
||||
},
|
||||
commands: [],
|
||||
paths: {
|
||||
applicationRoots: ['apps/web'],
|
||||
testRoots: ['tests'],
|
||||
documentationRoots: ['docs'],
|
||||
generated: ['dist'],
|
||||
protected: ['runtime'],
|
||||
excluded: ['node_modules'],
|
||||
},
|
||||
policies: {
|
||||
preserveBackwardCompatibility: true,
|
||||
newDependencies: 'justify',
|
||||
gitWrite: 'none',
|
||||
migrations: 'reversible-only',
|
||||
documentationRequired: true,
|
||||
networkAccess: 'forbidden',
|
||||
productionDataAccess: 'forbidden',
|
||||
},
|
||||
},
|
||||
},
|
||||
1,
|
||||
)
|
||||
}
|
||||
|
||||
describe.skipIf(!databaseIntegration)(
|
||||
'generated-run history integration',
|
||||
() => {
|
||||
const workspaceA = randomUUID()
|
||||
const workspaceB = randomUUID()
|
||||
const userId = randomUUID()
|
||||
const repositoryA = randomUUID()
|
||||
const repositoryB = randomUUID()
|
||||
const alphaSlug = `history-alpha-${randomUUID()}`
|
||||
const betaSlug = `history-beta-${randomUUID()}`
|
||||
let alphaVersionId: string
|
||||
let betaVersionId: string
|
||||
let profileRevisionA: string
|
||||
let profileRevisionB: string
|
||||
let store: DrizzleGeneratedRunStore
|
||||
const runIds = [randomUUID(), randomUUID(), randomUUID()]
|
||||
|
||||
async function insertVersion(slug: string) {
|
||||
const sql = getSqlClient()
|
||||
const [playbook] = await sql<{ id: string }[]>`
|
||||
insert into playbooks (
|
||||
workspace_id, logical_id, slug, namespace, source_type
|
||||
) values (
|
||||
${workspaceA}, ${slug}, ${slug}, ${`private-${workspaceA}`}, 'private'
|
||||
) returning id
|
||||
`
|
||||
const manifest = {
|
||||
metadata: { slug, version: '1.0.0', title: 'History fixture' },
|
||||
spec: { intent: { outcome: 'Verify history.' } },
|
||||
}
|
||||
const [version] = await sql<{ id: string }[]>`
|
||||
insert into playbook_versions (
|
||||
playbook_id, semantic_version, lifecycle, package_api_version,
|
||||
title, summary, category, risk_tier, package_json, template_text,
|
||||
content_digest, published_at, created_by
|
||||
) values (
|
||||
${playbook!.id}, '1.0.0', 'validated', 'devrunbook.io/v1alpha1',
|
||||
'History fixture', 'History fixture', 'testing', 'low',
|
||||
${JSON.stringify(manifest)}::jsonb, '# Task', ${'a'.repeat(64)},
|
||||
'2026-07-27T12:00:00.000Z', ${userId}
|
||||
) returning id
|
||||
`
|
||||
return version!.id
|
||||
}
|
||||
|
||||
async function insertRun(input: {
|
||||
id: string
|
||||
workspaceId: string
|
||||
playbookVersionId: string
|
||||
slug: string
|
||||
generatedAt: string
|
||||
repository?: {
|
||||
id: string
|
||||
revisionId: string
|
||||
document: RepositoryProfile
|
||||
}
|
||||
corruptRepository?: boolean
|
||||
}) {
|
||||
const sql = getSqlClient()
|
||||
const prompt = `# ${input.id}\n`
|
||||
const digest = createHash('sha256').update(prompt, 'utf8').digest('hex')
|
||||
const repositorySnapshot = input.corruptRepository
|
||||
? {
|
||||
revisionId: input.repository!.revisionId,
|
||||
repositoryId: input.repository!.id,
|
||||
revisionNumber: 1,
|
||||
contentDigest: input.repository!.document.metadata.contentDigest,
|
||||
profile: {},
|
||||
}
|
||||
: input.repository
|
||||
? {
|
||||
revisionId: input.repository.revisionId,
|
||||
repositoryId: input.repository.id,
|
||||
revisionNumber: 1,
|
||||
contentDigest: input.repository.document.metadata.contentDigest,
|
||||
profile: input.repository.document,
|
||||
}
|
||||
: null
|
||||
await sql`
|
||||
insert into generated_runs (
|
||||
id, workspace_id, playbook_version_id, playbook_snapshot_json,
|
||||
repository_profile_snapshot_json, normalized_input_json,
|
||||
policy_snapshot_json, provenance_json, lint_result_json,
|
||||
rendered_prompt, render_digest, idempotency_key, generated_by,
|
||||
generated_at
|
||||
) values (
|
||||
${input.id}, ${input.workspaceId}, ${input.playbookVersionId},
|
||||
${JSON.stringify({ slug: input.slug })}::jsonb,
|
||||
${repositorySnapshot === null ? null : JSON.stringify(repositorySnapshot)}::jsonb,
|
||||
'{}'::jsonb, '{}'::jsonb, '[]'::jsonb,
|
||||
${JSON.stringify({ exportReadiness: 'ready', findings: [] })}::jsonb,
|
||||
${prompt}, ${digest}, ${randomUUID()}, ${userId}, ${input.generatedAt}
|
||||
)
|
||||
`
|
||||
}
|
||||
|
||||
beforeAll(async () => {
|
||||
const sql = getSqlClient()
|
||||
await sql`
|
||||
insert into users (
|
||||
id, email, display_name, password_hash, instance_role, status
|
||||
) values (
|
||||
${userId}, ${`run-history-${userId}@example.invalid`}, 'Run history',
|
||||
'not-a-real-password-hash', 'user', 'active'
|
||||
)
|
||||
`
|
||||
await sql`
|
||||
insert into workspaces (id, name, type)
|
||||
values
|
||||
(${workspaceA}, 'Run history A', 'team'),
|
||||
(${workspaceB}, 'Run history B', 'team')
|
||||
`
|
||||
await sql`
|
||||
insert into repositories (id, workspace_id, display_name, source_type)
|
||||
values
|
||||
(${repositoryA}, ${workspaceA}, 'Repository A', 'manual'),
|
||||
(${repositoryB}, ${workspaceA}, 'Repository B', 'manual')
|
||||
`
|
||||
alphaVersionId = await insertVersion(alphaSlug)
|
||||
betaVersionId = await insertVersion(betaSlug)
|
||||
const profileA = profile('Repository A')
|
||||
const profileB = profile('Repository B')
|
||||
const [revisionA] = await sql<{ id: string }[]>`
|
||||
insert into repository_profile_revisions (
|
||||
repository_id, revision_number, profile_json, content_digest, created_by
|
||||
) values (
|
||||
${repositoryA}, 1, ${JSON.stringify(profileA)}::jsonb,
|
||||
${profileA.metadata.contentDigest!}, ${userId}
|
||||
) returning id
|
||||
`
|
||||
const [revisionB] = await sql<{ id: string }[]>`
|
||||
insert into repository_profile_revisions (
|
||||
repository_id, revision_number, profile_json, content_digest, created_by
|
||||
) values (
|
||||
${repositoryB}, 1, ${JSON.stringify(profileB)}::jsonb,
|
||||
${profileB.metadata.contentDigest!}, ${userId}
|
||||
) returning id
|
||||
`
|
||||
profileRevisionA = revisionA!.id
|
||||
profileRevisionB = revisionB!.id
|
||||
await insertRun({
|
||||
id: runIds[0]!,
|
||||
workspaceId: workspaceA,
|
||||
playbookVersionId: alphaVersionId,
|
||||
slug: alphaSlug,
|
||||
generatedAt: '2026-07-27T12:00:00.000Z',
|
||||
repository: {
|
||||
id: repositoryA,
|
||||
revisionId: profileRevisionA,
|
||||
document: profileA,
|
||||
},
|
||||
})
|
||||
await insertRun({
|
||||
id: runIds[1]!,
|
||||
workspaceId: workspaceA,
|
||||
playbookVersionId: alphaVersionId,
|
||||
slug: alphaSlug,
|
||||
generatedAt: '2026-07-27T12:00:00.000Z',
|
||||
})
|
||||
await insertRun({
|
||||
id: runIds[2]!,
|
||||
workspaceId: workspaceA,
|
||||
playbookVersionId: betaVersionId,
|
||||
slug: betaSlug,
|
||||
generatedAt: '2026-07-26T12:00:00.000Z',
|
||||
repository: {
|
||||
id: repositoryB,
|
||||
revisionId: profileRevisionB,
|
||||
document: profileB,
|
||||
},
|
||||
})
|
||||
store = new DrizzleGeneratedRunStore()
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
const sql = getSqlClient()
|
||||
await sql`delete from workspaces where id in (${workspaceA}, ${workspaceB})`
|
||||
await sql`delete from users where id = ${userId}`
|
||||
await closeDatabase()
|
||||
})
|
||||
|
||||
it('paginates generatedAt plus ID without duplicates or omissions', async () => {
|
||||
const seen: string[] = []
|
||||
let cursor: string | null = null
|
||||
do {
|
||||
const page = await store.listForWorkspace(workspaceA, {
|
||||
limit: 1,
|
||||
cursor,
|
||||
})
|
||||
seen.push(...page.items.map((item) => item.id))
|
||||
cursor = page.nextCursor
|
||||
} while (cursor)
|
||||
|
||||
const tied = runIds
|
||||
.slice(0, 2)
|
||||
.sort((left, right) => left.localeCompare(right, 'en'))
|
||||
expect(seen).toEqual([...tied, runIds[2]])
|
||||
expect(new Set(seen).size).toBe(3)
|
||||
await expect(store.listForWorkspace(workspaceB, {})).resolves.toEqual({
|
||||
items: [],
|
||||
nextCursor: null,
|
||||
})
|
||||
})
|
||||
|
||||
it('filters playbooks relationally and repositories through validated snapshots', async () => {
|
||||
const byPlaybook = await store.listForWorkspace(workspaceA, {
|
||||
playbookSlug: alphaSlug,
|
||||
})
|
||||
expect(byPlaybook.items.map((item) => item.id).sort()).toEqual(
|
||||
runIds.slice(0, 2).sort(),
|
||||
)
|
||||
const byRepository = await store.listForWorkspace(workspaceA, {
|
||||
repositoryId: repositoryA,
|
||||
})
|
||||
expect(byRepository.items.map((item) => item.id)).toEqual([runIds[0]])
|
||||
})
|
||||
|
||||
it('fails closed when a repository filter encounters a corrupt frozen snapshot', async () => {
|
||||
const corruptId = randomUUID()
|
||||
await insertRun({
|
||||
id: corruptId,
|
||||
workspaceId: workspaceA,
|
||||
playbookVersionId: alphaVersionId,
|
||||
slug: alphaSlug,
|
||||
generatedAt: '2026-07-28T12:00:00.000Z',
|
||||
repository: {
|
||||
id: repositoryA,
|
||||
revisionId: profileRevisionA,
|
||||
document: profile('Repository A'),
|
||||
},
|
||||
corruptRepository: true,
|
||||
})
|
||||
await expect(
|
||||
store.listForWorkspace(workspaceA, { repositoryId: repositoryA }),
|
||||
).rejects.toMatchObject({ code: 'generated_run_persistence_corrupt' })
|
||||
})
|
||||
},
|
||||
)
|
||||
Reference in New Issue
Block a user