Skip to content
Projects
Groups
Snippets
Help
Loading...
Sign in / Register
Toggle navigation
I
integrity_practice_management_new
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Registry
Registry
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
David Yang
integrity_practice_management_new
Commits
93b2457f
Commit
93b2457f
authored
Sep 24, 2026
by
drigle
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
update: 推送流程
parent
858cfe78
Expand all
Show whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
222 additions
and
0 deletions
+222
-0
test-auth-redirect.cjs
scripts/test-auth-redirect.cjs
+21
-0
test-self-inspection-flow.cjs
scripts/test-self-inspection-flow.cjs
+133
-0
test-self-inspection-push.cjs
scripts/test-self-inspection-push.cjs
+36
-0
SelfInspectionFlowPage.jsx
.../integrity_practice_management/SelfInspectionFlowPage.jsx
+0
-0
selfInspectionFlowService.js
...ntegrity_practice_management/selfInspectionFlowService.js
+0
-0
authRedirect.js
src/utils/authRedirect.js
+32
-0
No files found.
scripts/test-auth-redirect.cjs
0 → 100644
View file @
93b2457f
const assert = require('node:assert/strict');
const fs = require('node:fs');
const vm = require('node:vm');
const ts = require('typescript');
const result = {};
let navigations = [];
const origin = 'https://example.test';
const location = { origin, pathname: '/integrity_practice_management_new/self_inspection_form', href: origin + '/integrity_practice_management_new/self_inspection_form?id=flow%26one&type=editor#row2', replace: url => navigations.push(url) };
vm.runInNewContext(ts.transpileModule(fs.readFileSync('src/utils/authRedirect.js','utf8'), {compilerOptions:{module:ts.ModuleKind.CommonJS,target:ts.ScriptTarget.ES2022}}).outputText, {exports:result, URL, window:{location}, require:()=>({APPLICATION_BASE_PATH:'/integrity_practice_management_new/',ROUTE_TITLES:{'/notifications':'通知'},SELF_INSPECTION_FLOW_PATH:'/self_inspection_form'})});
const target = '/self_inspection_form?id=flow%26one&type=editor#row2';
assert.equal(result.normalizeAuthRedirect(location.href), target);
assert.equal(result.normalizeAuthRedirect(target), target);
assert.equal(new URLSearchParams(result.getLoginRoute(location.href).split('?')[1]).get('redirect'),target);
for(const input of ['https://evil.test/self_inspection_form','//evil.test/self_inspection_form','javascript:alert(1)','/login?redirect=/login','/another_app/form']) assert.equal(result.normalizeAuthRedirect(input),'/');
result.redirectToLogin();
assert.equal(navigations.length,1);
assert.ok(navigations[0].startsWith('/integrity_practice_management_new/login?redirect='));
location.pathname='/integrity_practice_management_new/login';
result.redirectToLogin();
assert.equal(navigations.length,1);
console.log('PASS: deep links, query/hash, base normalization, external/login-loop rejection, 401 redirect');
scripts/test-self-inspection-flow.cjs
0 → 100644
View file @
93b2457f
const assert = require('node:assert/strict');
const fs = require('node:fs');
const vm = require('node:vm');
const ts = require('typescript');
const source = fs.readFileSync('src/pages/integrity_practice_management/selfInspectionFlowService.js', 'utf8');
const compiled = ts.transpileModule(source, { compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 } }).outputText;
let message, rows, parents, calls, failure, denyField;
const clone = (value) => JSON.parse(JSON.stringify(value));
const subject = { fields: [{ name: 'self_inspection_result', isRequired: true }, { name: 'attachment', multiple: true }] };
const api = {
loadRecord: async (id) => clone(id === 'p1' ? parents[0] : message),
loadSubject: async () => clone(subject),
loadUser: async (id) => ({ _id: id, username: id, display_name: id }),
queryRecords: async ({ subject }) => ({ records: clone(subject === 'self_check_the_project_results' ? rows : parents) }),
updateRecord: async (id, data) => {
calls.push(['update', id, clone(data)]);
if (failure === 'save' && id === 'r1') throw Error('save failed');
if (id === 'm1') Object.assign(message.metadata, data.metadata);
},
batchUpdateRecords: async (data) => { calls.push(['batch', clone(data)]); },
executeAction: async (action, data) => {
calls.push([action, clone(data)]);
if (failure === 'oa') throw Error('oa failed');
return { data: { taskId: `task-${calls.length}` } };
},
createNotice: async (data) => { calls.push(['notice', clone(data)]); return { name: 'notice' }; },
sendNotice: async () => { if (failure === 'notice') throw Error('notice failed'); },
};
const exportsObject = {};
vm.runInNewContext(compiled, { exports: exportsObject, require(name) {
if (name.endsWith('DataService')) return api;
if (name.endsWith('subjectView')) return { getSelectOptions: () => [], isFieldDisabledByView: (field) => field === denyField };
if (name.endsWith('application')) return { getSelfInspectionFlowUrl: (name) => `https://test/integrity_practice_management_new/self_inspection_form?id=${name}` };
return { createIntegrityPracticeMaterial: async () => { calls.push(['material']); return { id: 'material1' }; } };
}});
const service = exportsObject;
function reset() {
message = { _id: 'm1', name: 'M001', subject: { name: 'message_reference_id' }, metadata: {
subject_name: 'self_check_the_project_results', restriction_value: ['r1'], reference_record_id: ['p1'],
editor: ['editor', 'editor2'], auditor: ['auditor', 'auditor2'], sender: 'sender',
editor_status: '未完成', auditor_status: '未完成', sender_status: '未完成', progress_id_editor: ['old-editor'],
}};
rows = [{ _id: 'r1', metadata: { self_inspection_result: '已检查' } }];
parents = [{ _id: 'p1', subject: { name: 'self_check_the_results_of_the_working_draft' }, metadata: { project_outcome: [{ _id: 'r1' }] } }]; calls = []; failure = null; denyField = null;
}
const user = (id) => ({ _id: id, username: id });
const transition = (action, id) => service.transitionSelfInspectionFlow({ messageName: 'M001', currentUser: user(id), action });
(async () => {
reset();
await assert.rejects(() => service.loadSelfInspectionFlow('M001', user('guest')), /参与人/);
message.metadata.subject_name = 'problem_rectification';
await assert.rejects(() => service.loadSelfInspectionFlow('M001', user('editor')), /不是廉洁/);
reset();
await assert.rejects(() => transition('approve', 'auditor'), /状态已变化/);
assert.equal(calls.length, 0);
await transition('submit', 'editor');
assert.equal(message.metadata.editor_status, '已完成');
assert.equal(calls.filter(([name]) => name === 'oa_notice:createProgress').length, 2);
assert.ok(calls.find(([name, value]) => name === 'notice' && value.content.includes('/integrity_practice_management_new/')));
await transition('withdraw', 'editor2');
assert.equal(message.metadata.editor_status, '未完成');
assert.equal(calls.filter(([name]) => name === 'notice').length, 1);
await transition('submit', 'editor');
await transition('reject', 'auditor2');
assert.equal(message.metadata.editor_status, '未完成');
assert.ok(calls.some(([name, value]) => name === 'batch' && value.data['metadata.review_status'] === '已驳回'));
assert.ok(!calls.some(([name, value]) => name === 'batch' && value.data['metadata.status'] === '已驳回'));
await transition('submit', 'editor');
await transition('approve', 'auditor');
await assert.rejects(() => transition('withdraw', 'editor'), /状态已变化/);
await transition('finalReject', 'sender');
await transition('submit', 'editor');
assert.equal(message.metadata.sender_status, '未完成');
await transition('approve', 'auditor');
await transition('finalApprove', 'sender');
assert.equal(service.getFlowPermissions(message, user('sender')).step, 'completed');
await assert.rejects(() => transition('finalApprove', 'sender'), /状态已变化/);
reset();
message.metadata.editor.push('sender');
assert.equal(service.getFlowPermissions(message, user('sender')).canSubmit, true);
await assert.rejects(() => service.saveSelfInspectionResults({ messageName: 'M001', currentUser: user('editor'), changes: { alien: { self_inspection_result: 'bad' } } }), /以外/);
rows[0].metadata.self_inspection_result = '';
await assert.rejects(() => transition('submit', 'editor'), /请填写/);
assert.equal(calls.length, 0);
reset();
assert.equal(service.getFlowPermissions(message, user('auditor')).canEdit, true);
message.metadata.editor_status = '已完成';
assert.equal(service.getFlowPermissions(message, user('editor')).canEdit, false);
assert.equal(service.getFlowPermissions(message, user('auditor')).canEdit, true);
message.metadata.auditor_status = '已完成';
assert.equal(service.getFlowPermissions(message, user('auditor')).canEdit, false);
assert.equal(service.getFlowPermissions(message, user('sender')).canEdit, true);
reset(); denyField = 'metadata.self_inspection_result';
await assert.rejects(() => service.saveSelfInspectionResults({ messageName: 'M001', currentUser: user('editor'), changes: { r1: { self_inspection_result: 'unauthorized' } } }), /视图不允许/);
assert.equal(calls.length, 0);
reset(); message.metadata.restriction_value = [];
const recovered = await service.loadSelfInspectionFlow('M001', user('editor'));
assert.equal(recovered.rows[0]._id, 'r1');
assert.equal(calls.length, 0, 'reading an old flow must not write');
await service.saveSelfInspectionResults({ messageName: 'M001', currentUser: user('editor'), changes: { r1: { self_inspection_result: 'recovered' } } });
await transition('submit', 'editor');
assert.ok(calls.some(([kind,data]) => kind === 'batch' && data.filter._id.$in.includes('r1')));
reset(); parents[0].metadata.project_outcome = ['other'];
assert.equal((await service.loadSelfInspectionFlow('M001',user('editor'))).rows[0]._id,'r1', 'explicit flow IDs remain authoritative');
reset(); message.metadata.restriction_value = []; parents[0].metadata.project_outcome = [];
await assert.rejects(() => service.loadSelfInspectionFlow('M001', user('editor')), /均没有自查项目/);
reset();
assert.equal(service.getFlowApprovalStatus(message, '已审批'), '未审批');
assert.equal(service.getFlowApprovalStatus(message, '已审批', true), '未审批');
message.metadata.editor_status = '已完成';
assert.equal(service.getFlowApprovalStatus(message, '已审批'), '未审批');
message.metadata.auditor_status = '已完成';
assert.equal(service.getFlowApprovalStatus(message, '已审批'), '已审批');
assert.equal(service.getFlowApprovalStatus(message, '已审批', true), '未审批');
message.metadata.sender_status = '已完成';
assert.equal(service.getFlowApprovalStatus(message, '已审批', true), '已审批');
message.metadata.editor_status = '未完成';
assert.equal(service.getFlowApprovalStatus(message, '已驳回', true), '已驳回');
assert.equal(service.getFlowApprovalStatus(message, '已审批'), '未审批');
reset(); failure = 'oa';
await assert.rejects(() => transition('submit', 'editor'), /oa failed/);
assert.equal(message.metadata.editor_status, '未完成');
reset(); failure = 'notice';
await assert.rejects(() => transition('submit', 'editor'), /状态已更新/);
assert.equal(message.metadata.editor_status, '已完成');
reset(); failure = 'save';
const pending = { r1: [{ fileAsset: { url: '/file.pdf' }, materialRecordId: null }] };
const save = () => service.saveSelfInspectionResults({ messageName: 'M001', currentUser: user('editor'), changes: { r1: { attachment: [{ name: 'file.pdf', url: '/file.pdf' }] } }, pendingMaterials: pending });
await assert.rejects(save, /save failed/); failure = null; await save();
assert.equal(calls.filter(([name]) => name === 'material').length, 1);
assert.equal(pending.r1[0].materialRecordId, 'material1');
console.log('PASS: permissions, wrong subject, transitions, withdrawal, rejection/resubmission, terminal state, required fields, OA failure, partial notification failure, material retry');
})().catch((error) => { console.error(error); process.exitCode = 1; });
scripts/test-self-inspection-push.cjs
0 → 100644
View file @
93b2457f
const assert = require('node:assert/strict');
const fs = require('node:fs');
const vm = require('node:vm');
const ts = require('typescript');
const exportsObject = {};
let writes = [], empty = false;
const toArray = value => value == null ? [] : Array.isArray(value) ? value : [value];
const api = {
loadRecord: async id => ({ _id: id, name: id, title: id, metadata: { project_outcome: empty && id === 'p2' ? [] : [{ _id: 'r1' }, 'r2', 'r2'] } }),
getSubjectView: async () => [{ name: 'view' }],
batchUpdateRecords: async value => writes.push(['batch', value]),
updateRecord: async (...args) => writes.push(['update', ...args]),
createRecord: async value => { writes.push(['create', value]); return { _id: 'flow', name: 'flow' }; },
loadUser: async id => ({ _id: id, username: id }),
executeAction: async (...args) => { writes.push(['oa', ...args]); return { data: { taskId: 'task' } }; },
createNotice: async value => { writes.push(['notice', value]); return { name: 'notice' }; },
sendNotice: async () => {},
};
const compiled = ts.transpileModule(fs.readFileSync('src/pages/integrity_practice_management/integrityPracticeWorkbenchService.js', 'utf8'), { compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 } }).outputText;
vm.runInNewContext(compiled, { exports: exportsObject, require(name) {
if (name.endsWith('DataService')) return api;
if (name.endsWith('subjectView')) return { toArray };
if (name.endsWith('application')) return { getSelfInspectionFlowUrl: () => '/flow' };
if (name === 'dayjs') return { default: () => ({ format: () => '2026-09-20' }) };
return {};
}});
const push = records => exportsObject.executeConfiguredAction('integrityRiskManagerService.pushSelfInspectionForm', { selectedRecords: records, userInfo: { _id: 'sender' }, assignment: { fillers: ['editor'], auditors: ['auditor'] } });
(async () => {
await push([{ _id: 'p1', metadata: {} }]);
const created = writes.find(([kind]) => kind === 'create')[1];
assert.equal(JSON.stringify(created.metadata.restriction_value), '["r1","r2"]');
writes = []; empty = true;
await assert.rejects(() => push([{ _id: 'p1' }, { _id: 'p2' }]), /没有关联自查项目/);
assert.equal(writes.length, 0);
console.log('PASS: push hydrates omitted list references, normalizes IDs, and validates entire batch before any writes');
})().catch(error => { console.error(error); process.exitCode = 1; });
src/pages/integrity_practice_management/SelfInspectionFlowPage.jsx
0 → 100644
View file @
93b2457f
This diff is collapsed.
Click to expand it.
src/pages/integrity_practice_management/selfInspectionFlowService.js
0 → 100644
View file @
93b2457f
This diff is collapsed.
Click to expand it.
src/utils/authRedirect.js
0 → 100644
View file @
93b2457f
import
{
APPLICATION_BASE_PATH
,
ROUTE_TITLES
,
SELF_INSPECTION_FLOW_PATH
}
from
'@/config/application'
;
// Router navigation uses paths without the deployment base; browser navigation
// uses the base exactly once. Keep query/hash intact throughout the round trip.
export
function
normalizeAuthRedirect
(
value
,
origin
=
window
.
location
.
origin
)
{
if
(
!
value
)
return
'/'
;
try
{
const
target
=
new
URL
(
value
,
origin
);
if
(
target
.
origin
!==
origin
)
return
'/'
;
const
base
=
APPLICATION_BASE_PATH
.
replace
(
/
\/
+$/
,
''
);
let
path
=
target
.
pathname
;
if
(
path
===
base
)
path
=
'/'
;
else
if
(
path
.
startsWith
(
`
${
base
}
/`
))
path
=
path
.
slice
(
base
.
length
);
path
=
path
.
replace
(
/
\/
+$/
,
''
)
||
'/'
;
if
(
path
!==
'/'
&&
path
!==
SELF_INSPECTION_FLOW_PATH
&&
!
Object
.
hasOwn
(
ROUTE_TITLES
,
path
))
return
'/'
;
return
`
${
path
}${
target
.
search
}${
target
.
hash
}
`
;
}
catch
{
return
'/'
;
}
}
export
function
getLoginRoute
(
value
,
origin
=
window
.
location
.
origin
)
{
return
`/login?redirect=
${
encodeURIComponent
(
normalizeAuthRedirect
(
value
,
origin
))}
`
;
}
export
function
redirectToLogin
()
{
const
base
=
APPLICATION_BASE_PATH
.
replace
(
/
\/
+$/
,
''
);
// A failed login/captcha request must not reload login or nest redirect URLs.
if
(
window
.
location
.
pathname
.
replace
(
/
\/
+$/
,
''
)
===
`
${
base
}
/login`
||
window
.
location
.
pathname
===
'/login'
)
return
;
window
.
location
.
replace
(
`
${
base
}${
getLoginRoute
(
window
.
location
.
href
)}
`
);
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment