gongxiangp2/scripts/test-salary-positions-local.ts

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 })