This commit is contained in:
@@ -0,0 +1,160 @@
|
||||
import { randomUUID } from 'node:crypto'
|
||||
import { mkdtemp, rm } from 'node:fs/promises'
|
||||
import { tmpdir } from 'node:os'
|
||||
import path from 'node:path'
|
||||
|
||||
import {
|
||||
createGeneratedArtifact,
|
||||
downloadGeneratedArtifact,
|
||||
} from '../../packages/application/src/index'
|
||||
import { LocalArtifactStorage } from '../../packages/artifacts/src/index'
|
||||
import {
|
||||
closeDatabase,
|
||||
DrizzleGeneratedArtifactStore,
|
||||
DrizzleWorkspaceAuthorizationLookup,
|
||||
getSqlClient,
|
||||
} from '../../packages/db/src/index'
|
||||
import { afterAll, describe, expect, it } from 'vitest'
|
||||
|
||||
const databaseUrl = process.env.DATABASE_URL
|
||||
const integration = databaseUrl ? describe : describe.skip
|
||||
const cleanupRoots: string[] = []
|
||||
|
||||
afterAll(async () => {
|
||||
await closeDatabase()
|
||||
await Promise.all(
|
||||
cleanupRoots.splice(0).map((root) => rm(root, { recursive: true })),
|
||||
)
|
||||
})
|
||||
|
||||
integration('generated artifact persistence', () => {
|
||||
it('persists authorized metadata and restart-readable verified bytes', async () => {
|
||||
const sql = getSqlClient(databaseUrl)
|
||||
const userId = randomUUID()
|
||||
const workspaceId = randomUUID()
|
||||
const otherWorkspaceId = randomUUID()
|
||||
const playbookId = randomUUID()
|
||||
const versionId = randomUUID()
|
||||
const runId = randomUUID()
|
||||
const artifactId = randomUUID()
|
||||
const namespace = `artifact-integration-${randomUUID()}`
|
||||
const artifactRoot = await mkdtemp(
|
||||
path.join(tmpdir(), 'devrunbook-artifact-integration-'),
|
||||
)
|
||||
cleanupRoots.push(artifactRoot)
|
||||
|
||||
await sql.begin(async (transaction) => {
|
||||
await transaction`
|
||||
insert into users (id, email, display_name, password_hash, instance_role)
|
||||
values (${userId}, ${`${userId}@example.invalid`}, 'Artifact Integration', 'not-a-login-credential', 'user')
|
||||
`
|
||||
await transaction`
|
||||
insert into workspaces (id, name)
|
||||
values (${workspaceId}, 'Artifact Workspace'), (${otherWorkspaceId}, 'Other Workspace')
|
||||
`
|
||||
await transaction`
|
||||
insert into workspace_memberships (user_id, workspace_id, role)
|
||||
values (${userId}, ${workspaceId}, 'editor')
|
||||
`
|
||||
await transaction`
|
||||
insert into playbooks (id, workspace_id, logical_id, slug, namespace, source_type)
|
||||
values (${playbookId}, ${workspaceId}, 'artifact-integration', 'artifact-integration', ${namespace}, 'private')
|
||||
`
|
||||
await transaction`
|
||||
insert into playbook_versions (
|
||||
id, playbook_id, semantic_version, lifecycle, package_api_version,
|
||||
title, summary, category, risk_tier, package_json, template_text,
|
||||
content_digest, created_by
|
||||
) values (
|
||||
${versionId}, ${playbookId}, '1.0.0', 'validated',
|
||||
'devrunbook.playbook/v1.2', 'Artifact integration',
|
||||
'Integration fixture', 'test', 'low', '{}'::jsonb, '# Test',
|
||||
${'a'.repeat(64)}, ${userId}
|
||||
)
|
||||
`
|
||||
await transaction`
|
||||
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
|
||||
) values (
|
||||
${runId}, ${workspaceId}, ${versionId}, '{}'::jsonb, null,
|
||||
'{}'::jsonb, '{}'::jsonb, '[]'::jsonb,
|
||||
'{"exportReadiness":"ready","findings":[]}'::jsonb,
|
||||
'# Test', ${'b'.repeat(64)}, ${`artifact-${artifactId}`}, ${userId}
|
||||
)
|
||||
`
|
||||
})
|
||||
|
||||
const metadata = new DrizzleGeneratedArtifactStore()
|
||||
const authorization = new DrizzleWorkspaceAuthorizationLookup()
|
||||
const content = new TextEncoder().encode('# Test\n')
|
||||
const created = await createGeneratedArtifact(
|
||||
{
|
||||
authorization,
|
||||
metadata,
|
||||
storage: new LocalArtifactStorage(artifactRoot),
|
||||
now: () => new Date('2026-07-27T12:00:00.000Z'),
|
||||
},
|
||||
{
|
||||
actor: { userId },
|
||||
workspaceId,
|
||||
artifactId,
|
||||
runId,
|
||||
artifactType: 'markdown',
|
||||
filename: 'test.md',
|
||||
mediaType: 'text/markdown; charset=utf-8',
|
||||
content,
|
||||
},
|
||||
)
|
||||
expect(created.created).toBe(true)
|
||||
|
||||
const [persisted] = await sql<
|
||||
{ id: string; runId: string; sha256: string; sizeBytes: string }[]
|
||||
>`
|
||||
select id, run_id as "runId", sha256, size_bytes as "sizeBytes"
|
||||
from generated_artifacts
|
||||
where id = ${artifactId}
|
||||
`
|
||||
expect(persisted).toMatchObject({
|
||||
id: artifactId,
|
||||
runId,
|
||||
sha256: created.artifact.sha256,
|
||||
sizeBytes: String(content.byteLength),
|
||||
})
|
||||
|
||||
const downloaded = await downloadGeneratedArtifact(
|
||||
{
|
||||
authorization,
|
||||
metadata: new DrizzleGeneratedArtifactStore(),
|
||||
storage: new LocalArtifactStorage(artifactRoot),
|
||||
},
|
||||
{ actor: { userId }, workspaceId, artifactId },
|
||||
)
|
||||
expect(downloaded.content).toEqual(content)
|
||||
expect(downloaded.artifact.sha256).toHaveLength(64)
|
||||
await expect(
|
||||
metadata.listByRunInWorkspace(runId, workspaceId),
|
||||
).resolves.toMatchObject([
|
||||
{ id: artifactId, runId, workspaceId, sha256: created.artifact.sha256 },
|
||||
])
|
||||
await expect(
|
||||
metadata.listByRunInWorkspace(runId, otherWorkspaceId),
|
||||
).resolves.toEqual([])
|
||||
|
||||
await expect(
|
||||
downloadGeneratedArtifact(
|
||||
{
|
||||
authorization,
|
||||
metadata,
|
||||
storage: new LocalArtifactStorage(artifactRoot),
|
||||
},
|
||||
{ actor: { userId }, workspaceId: otherWorkspaceId, artifactId },
|
||||
),
|
||||
).rejects.toMatchObject({ code: 'workspace_access_denied' })
|
||||
|
||||
await sql`delete from workspaces where id in (${workspaceId}, ${otherWorkspaceId})`
|
||||
await sql`delete from users where id = ${userId}`
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user