62 lines
5.3 KiB
TypeScript
62 lines
5.3 KiB
TypeScript
/** Synthetic fixtures only; never points at the local payroll database or production. */
|
|
import assert from 'node:assert/strict'
|
|
import { randomUUID } from 'node:crypto'
|
|
import { PrismaClient } from '@prisma/client'
|
|
async function main() {
|
|
const url = new URL(process.env.DATABASE_URL || '')
|
|
if (url.hostname !== '127.0.0.1' || url.pathname !== '/salary_positions_contract_20261028') throw new Error('Isolated contract database guard refused target')
|
|
const db = new PrismaClient({ log: [] })
|
|
const { createSalaryPosition, updateSalaryPosition, deleteSalaryPosition, listSalaryPositions } = await import('../lib/salary-positions')
|
|
const { createSalaryRecord, updateSalaryRecord, deleteSalaryRecord } = await import('../lib/personnel-salary')
|
|
const { prisma } = await import('../lib/prisma')
|
|
let checks = 0
|
|
const verify = (condition: unknown) => { assert.ok(condition); checks++ }
|
|
const reject = async (work: Promise<unknown>, status: number) => { await assert.rejects(work, (error: any) => error.status === status); checks++ }
|
|
const admin = 'qa-position-admin', other = 'qa-position-denied'
|
|
const readers = ['c156778c-307d-42aa-89d3-f785aeb3ef86', '463c5a60-c8a8-45dd-b4cd-4a31a6b1b84f', 'c041d8d8-93db-41f1-984d-5f76f114da48', '2b1ccc12-844c-4eff-8006-58607157efc1']
|
|
try {
|
|
verify(await db.user.count() === 0)
|
|
for (const id of [admin, other, ...readers]) await db.user.create({ data: { id, username: id, email: `${id}@example.test`, password: 'not-a-login-hash', isSuperAdmin: id === admin, isActive: true, status: 'active' } })
|
|
await db.organizationUnit.create({ data: { name: '品牌中心', sourceDeptId: 9999, path: '公司/品牌中心' } })
|
|
const person = await db.performanceSurveyEmployeeProfile.create({ data: { id: randomUUID(), realName: '合成人员', flowerName: '测试', displayName: '合成人员', jobNumber: 'QA-position', employmentState: 'ACTIVE', departmentName: '品牌中心', departmentPath: '公司/品牌中心' } })
|
|
const value = { name: '合成岗位', description: '', salaryRole: 'ai-planning', dimensions: [] }
|
|
const job = await createSalaryPosition(value, admin)
|
|
verify(job.revision === 1 && job.dimensions.length === 0)
|
|
await reject(createSalaryPosition(value, admin), 409)
|
|
for (const reader of readers) {
|
|
verify(!(await listSalaryPositions(reader)).canEdit)
|
|
await reject(createSalaryPosition({ ...value, name: '只读拒绝' }, reader), 403)
|
|
await reject(updateSalaryPosition(job.id, { ...value, revision: 1 }, reader), 403)
|
|
await reject(deleteSalaryPosition(job.id, { revision: 1 }, reader), 403)
|
|
}
|
|
await reject(listSalaryPositions(other), 403)
|
|
const dimensions = [{ id: randomUUID(), name: '定量', weight: '100', metrics: [{ id: randomUUID(), name: '按时交付', weight: '100', description: '', source: { kind: 'on-time' } }] }]
|
|
const updated = await updateSalaryPosition(job.id, { ...value, name: '岗位改名', dimensions, revision: 1 }, admin)
|
|
verify(updated.id === job.id && updated.revision === 2)
|
|
await reject(updateSalaryPosition(job.id, { ...value, revision: 1 }, admin), 409)
|
|
await reject(deleteSalaryPosition(job.id, { revision: 1 }, admin), 409)
|
|
const race = await Promise.allSettled([0, 1].map(() => updateSalaryPosition(job.id, { ...value, dimensions, revision: 2 }, admin)))
|
|
verify(race.filter(r => r.status === 'fulfilled').length === 1)
|
|
const latest = (await listSalaryPositions(admin)).positions[0]
|
|
const fields = { name: '合成人员', flowerName: '测试', department: '品牌中心', brand: '', baseSalary: '0', gradeSalary: '0.01', performanceSalary: '100.99', positionId: job.id }
|
|
const record = await createSalaryRecord({ ...fields, employeeProfileId: person.id }, admin)
|
|
verify(record.positionId === job.id)
|
|
await reject(createSalaryRecord({ ...fields, employeeProfileId: person.id }, admin), 409)
|
|
await reject(deleteSalaryPosition(job.id, { revision: latest.revision }, admin), 409)
|
|
const technical = await createSalaryPosition({ ...value, name: '技术岗', salaryRole: 'ai-technical' }, admin)
|
|
const moved = await updateSalaryRecord(record.id, { ...fields, positionId: technical.id, revision: record.revision }, admin)
|
|
verify(moved.positionId === technical.id && moved.gradeSalary === '0.01' && moved.performanceSalary === '100.99')
|
|
await deleteSalaryPosition(job.id, { revision: latest.revision }, admin)
|
|
verify((await db.salaryPositionConfig.findUniqueOrThrow({ where: { id: job.id } })).deletedAt !== null)
|
|
await reject(updateSalaryRecord(record.id, { ...fields, revision: moved.revision }, admin), 409)
|
|
verify((await db.salaryPositionConfigAudit.count({ where: { positionId: job.id } })) === 4)
|
|
await deleteSalaryRecord(record.id, { revision: moved.revision }, admin)
|
|
await deleteSalaryPosition(technical.id, { revision: technical.revision }, admin)
|
|
await db.user.update({ where: { id: admin }, data: { isSuperAdmin: false } })
|
|
await reject(listSalaryPositions(admin), 403)
|
|
await reject(createSalaryPosition(value, admin), 403)
|
|
console.log(JSON.stringify({ passed: checks, fixtures: 'synthetic', database: 'isolated local contract' }))
|
|
} finally { await db.$disconnect(); await prisma.$disconnect() }
|
|
}
|
|
main().catch(error => { console.error(error instanceof Error ? error.message : 'Local contract check failed'); process.exitCode = 1 })
|