import type { FilePermissionPolicy } from './filePermissions';
|
|
import { reactive } from 'vue';
|
|
import { describe, expect, it } from 'vitest';
|
|
import {
|
filePermissionDesignerContext,
|
isEditableFilePermissionPolicy,
|
retainFilePermissions,
|
validateDesignerFilePermissions,
|
validateFilePermissionPolicy,
|
} from './filePermissions';
|
|
const fields = { form: new Set(['files', 'rows-files']) };
|
const nodes = new Set(['node']);
|
function policy(): FilePermissionPolicy {
|
return {
|
version: 1,
|
rules: [
|
{
|
id: 'r1',
|
formId: 'form',
|
field: 'rows-files',
|
subject: 'PARTICIPANTS',
|
trigger: 'ENTERED',
|
triggerNodeId: 'node',
|
onReturn: 'KEEP',
|
actions: { VIEW: 'ALLOW', DOWNLOAD: 'DENY' },
|
},
|
],
|
};
|
}
|
|
describe('workflow file permission configuration', () => {
|
it('keeps same-form reactive upload configuration without sharing mutable state', () => {
|
const previous = reactive({ jnpfKey: 'uploadFile', filePermissions: { DOWNLOAD: 'DENY' } });
|
const next = retainFilePermissions(previous, { __config__: { jnpfKey: 'uploadFile' } }, true);
|
expect(next.filePermissions).toEqual({ DOWNLOAD: 'DENY' });
|
next.filePermissions!.DOWNLOAD = 'ALLOW';
|
expect(previous.filePermissions.DOWNLOAD).toBe('DENY');
|
});
|
it('does not carry permissions across forms or component types', () => {
|
const previous = { jnpfKey: 'uploadFile', filePermissions: { DOWNLOAD: 'DENY' } };
|
expect(retainFilePermissions(previous, { __config__: { jnpfKey: 'uploadFile' } }, false)).toEqual({});
|
expect(retainFilePermissions(previous, { __config__: { jnpfKey: 'input' } }, true)).toEqual({});
|
expect(retainFilePermissions({ ...previous, jnpfKey: 'input' }, { __config__: { jnpfKey: 'uploadFile' } }, true)).toEqual({});
|
});
|
it('retains malformed configuration for validation instead of dropping it', () => {
|
expect(retainFilePermissions({ jnpfKey: 'uploadFile', filePermissions: null }, { __config__: { jnpfKey: 'uploadFile' } }, true)).toEqual({
|
filePermissions: null,
|
});
|
});
|
it('does not add configuration to legacy nodes', () => {
|
expect(retainFilePermissions(undefined, { __config__: { jnpfKey: 'uploadFile' } }, true)).toEqual({});
|
});
|
it('round-trips persistent child-field rules through JSON', () => {
|
expect(validateFilePermissionPolicy(structuredClone(policy()), fields, nodes)).toEqual([]);
|
});
|
it('rejects deleted fields and nodes', () => {
|
expect(validateFilePermissionPolicy(policy(), { form: new Set(['files']) }, nodes)).toHaveLength(1);
|
expect(validateFilePermissionPolicy(policy(), fields, new Set())).toHaveLength(1);
|
});
|
it('requires explicit behavior after return', () => {
|
const value = policy();
|
delete value.rules[0]!.onReturn;
|
expect(validateFilePermissionPolicy(value, fields, nodes)).toHaveLength(1);
|
});
|
it('rejects duplicate rules and malformed actions', () => {
|
const value = policy();
|
value.rules.push({ ...value.rules[0]! });
|
expect(validateFilePermissionPolicy(value, fields, nodes)).toHaveLength(1);
|
(value.rules[0]!.actions as any).DOWNLOAD = true;
|
expect(validateFilePermissionPolicy(value, fields, nodes).length).toBeGreaterThan(1);
|
});
|
it('validates subject nodes and version without silently resetting permissions', () => {
|
const value = policy();
|
value.rules[0]!.subject = 'NODE_PARTICIPANTS';
|
expect(validateFilePermissionPolicy(value, fields, nodes)).toHaveLength(1);
|
value.rules[0]!.subjectNodeId = 'node';
|
expect(validateFilePermissionPolicy(value, fields, nodes)).toEqual([]);
|
expect(validateFilePermissionPolicy({ ...value, version: 2 } as any, fields, nodes)).toHaveLength(1);
|
});
|
it('accepts the mutually exclusive non-current-handler subject without a subject node', () => {
|
const value = policy();
|
value.rules[0]!.subject = 'NON_CURRENT_HANDLERS';
|
expect(validateFilePermissionPolicy(value, fields, nodes)).toEqual([]);
|
});
|
});
|
|
describe('designer file permission boundary', () => {
|
const live = new Set(['gateway', 'node', 'start']);
|
function data(): any {
|
return {
|
global: {
|
allFormMap: {
|
form_form: [
|
{ id: 'files', fullName: '主文件', __config__: { jnpfKey: 'uploadFile' } },
|
{ id: 'rows-files', fullName: '记录-文件', __config__: { jnpfKey: 'uploadFile' } },
|
{ id: 'name', __config__: { jnpfKey: 'input' } },
|
],
|
form_foreign: [{ id: 'files', __config__: { jnpfKey: 'uploadFile' } }],
|
},
|
},
|
start: { type: 'start', formId: 'form', formName: '审核表单' },
|
node: { type: 'approver', nodeName: '确认' },
|
gateway: { type: 'gateway', formId: 'foreign' },
|
deleted: { type: 'approver', formId: 'foreign' },
|
};
|
}
|
it('offers native main/child upload fields only from live human node forms', () => {
|
const context = filePermissionDesignerContext(data(), live);
|
expect(context.attachments.map((item) => [item.formId, item.field])).toEqual([
|
['form', 'files'],
|
['form', 'rows-files'],
|
]);
|
expect(context.nodeIds).toEqual(new Set(['node', 'start']));
|
expect(context.attachments[1]!.label).toBe('审核表单 / 记录-文件');
|
});
|
it('keeps unconfigured legacy flows of every type unchanged', () => {
|
const value = data();
|
const original = JSON.stringify(value);
|
for (const type of [0, 1, 2, 3]) expect(validateDesignerFilePermissions(value, live, type)).toEqual([]);
|
expect(JSON.stringify(value)).toBe(original);
|
});
|
it('validates native serialized config and blocks stale fields after a form switch', () => {
|
const value = data();
|
value.global.filePermissionPolicy = policy();
|
expect(validateDesignerFilePermissions(structuredClone(value), live, 0)).toEqual([]);
|
value.start.formId = 'foreign';
|
expect(validateDesignerFilePermissions(value, live, 0)).toContain('附件字段不存在:r1');
|
});
|
it('rejects duplicate IDs across nodes but ignores removed nodes', () => {
|
const value = data();
|
value.global.filePermissionPolicy = policy();
|
value.deleted.filePermissionPolicy = policy();
|
expect(validateDesignerFilePermissions(value, live, 1)).toEqual([]);
|
value.node.filePermissionPolicy = policy();
|
expect(validateDesignerFilePermissions(value, live, 1)).toContain('文件规则 ID 重复:r1');
|
});
|
it('blocks unsupported flow types and reserved rule IDs while accepting return release', () => {
|
const value = data();
|
value.global.filePermissionPolicy = policy();
|
expect(validateDesignerFilePermissions(value, live, 3)).toContain('当前流程类型暂不支持文件权限规则');
|
value.global.filePermissionPolicy.rules[0].onReturn = 'RELEASE';
|
value.global.filePermissionPolicy.rules[0].id = '@field/node/files';
|
expect(validateDesignerFilePermissions(value, live, 0)).toEqual(['文件规则使用了保留标识']);
|
});
|
it('rejects malformed data without mutating or throwing', () => {
|
for (const broken of [
|
{ version: 2, rules: [] },
|
{ version: 1, rules: [null] },
|
{ version: 1, rules: [{ id: 'r1', actions: null }] },
|
]) {
|
const value = data();
|
value.global.filePermissionPolicy = broken;
|
const before = JSON.stringify(value);
|
expect(isEditableFilePermissionPolicy(broken)).toBe(false);
|
expect(validateDesignerFilePermissions(value, live, 0).length).toBeGreaterThan(0);
|
expect(JSON.stringify(value)).toBe(before);
|
}
|
});
|
});
|