289 lines
10 KiB
JavaScript
289 lines
10 KiB
JavaScript
import test from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import {
|
|
harness,
|
|
props,
|
|
text,
|
|
} from './helpers/technical-incident-handling.mjs';
|
|
const note = 'PRIVATE SYNTHETIC prepared note with evidence.';
|
|
test('the actual scan incident detail identifies its failure and retains the ordinary technical treatment command', () => {
|
|
const initial = props();
|
|
const h = harness(
|
|
props({
|
|
incident: {
|
|
...initial.incident,
|
|
operation: 'scan_document',
|
|
error_code: 'scan_worker_failed',
|
|
},
|
|
}),
|
|
);
|
|
assert.match(text(h.tree), /Análise de segurança de documento/);
|
|
assert.match(text(h.tree), /Falha na execução da análise de segurança/);
|
|
h.change(note);
|
|
h.click('Assumir tratamento');
|
|
assert.equal(h.sent[0].data.action, 'claim');
|
|
assert.equal(
|
|
h.sent[0].url,
|
|
'/suporte-tecnico/incidentes/' + initial.incident.reference,
|
|
);
|
|
});
|
|
function prepare(h, action = 'Assumir tratamento') {
|
|
h.change(note);
|
|
return h.click(action);
|
|
}
|
|
function next(h, request = h.sent.at(-1), patch = {}) {
|
|
return {
|
|
...h.props,
|
|
commands: [
|
|
...h.props.commands,
|
|
{
|
|
key: request.data.operation_key,
|
|
version: request.data.version + 1,
|
|
},
|
|
],
|
|
incident: {
|
|
...h.props.incident,
|
|
handling: {
|
|
...h.props.incident.handling,
|
|
version: request.data.version + 1,
|
|
status: 'in_progress',
|
|
assignee_user_id: h.props.actorId,
|
|
assignee_name: 'SYNTHETIC support',
|
|
updated_at: '2026-11-03',
|
|
...patch,
|
|
},
|
|
},
|
|
};
|
|
}
|
|
function result(h, request = h.sent.at(-1), patch = {}) {
|
|
return {
|
|
reference: h.props.incident.reference,
|
|
actor_id: h.props.actorId,
|
|
operation_key: request.data.operation_key,
|
|
result_version: request.data.version + 1,
|
|
handling: next(h, request).incident.handling,
|
|
...patch,
|
|
};
|
|
}
|
|
test('commands match the technical state and a note is required without sending an empty request', () => {
|
|
const h = harness();
|
|
h.click('Assumir tratamento');
|
|
assert.equal(h.sent.length, 0);
|
|
assert.match(text(h.tree), /10 a 3000/);
|
|
const resolved = harness(
|
|
props({
|
|
incident: {
|
|
...props().incident,
|
|
handling: {
|
|
...props().incident.handling,
|
|
status: 'resolved',
|
|
version: 2,
|
|
},
|
|
},
|
|
}),
|
|
);
|
|
assert.ok(resolved.button('Reabrir incidente'));
|
|
assert.equal(resolved.button('Resolver incidente'), undefined);
|
|
assert.equal(resolved.button('Assumir tratamento'), undefined);
|
|
});
|
|
test('a command freezes the analysed version and a rapid second click sends no duplicate', () => {
|
|
const h = harness(),
|
|
request = prepare(h);
|
|
h.click('Resolver incidente');
|
|
assert.equal(h.sent.length, 1);
|
|
assert.equal(request.data.version, 0);
|
|
assert.equal(request.data.action, 'claim');
|
|
assert.equal(request.data.note, note);
|
|
});
|
|
test('confirmation preserves a note edited during the request and reloads the authorised detail', async () => {
|
|
const h = harness(),
|
|
request = prepare(h);
|
|
h.change('PRIVATE SYNTHETIC later note');
|
|
await h.reply(result(h, request), request);
|
|
assert.equal(h.note, 'PRIVATE SYNTHETIC later note');
|
|
assert.equal(h.reloads.length, 1);
|
|
h.refreshed(next(h, request));
|
|
const second = h.click('Acrescentar nota');
|
|
assert.equal(second.data.version, 1);
|
|
assert.notEqual(second.data.operation_key, request.data.operation_key);
|
|
});
|
|
test('a lost response repeats exactly the frozen command and leaves later text intact', async () => {
|
|
const h = harness(),
|
|
request = prepare(h);
|
|
await h.fail();
|
|
h.change('PRIVATE SYNTHETIC later note');
|
|
const retry = h.click('Repetir o registo original');
|
|
assert.deepEqual(retry.data, request.data);
|
|
await h.reply(result(h, retry), retry);
|
|
assert.equal(h.note, 'PRIVATE SYNTHETIC later note');
|
|
});
|
|
test('an authorised refresh confirms a committed command without another write', async () => {
|
|
const h = harness(),
|
|
request = prepare(h);
|
|
await h.fail(503);
|
|
h.click('Actualizar incidente');
|
|
assert.deepEqual(h.reloads[0].only, [
|
|
'incident',
|
|
'history',
|
|
'commands',
|
|
'actorId',
|
|
]);
|
|
h.refreshed(next(h, request));
|
|
assert.equal(h.sent.length, 1);
|
|
assert.equal(h.note, '');
|
|
assert.equal(h.leave(), false);
|
|
});
|
|
test('a mismatched success remains uncertain and cannot erase the prepared note', async () => {
|
|
const h = harness(),
|
|
request = prepare(h);
|
|
await h.reply(result(h, request, { actor_id: 99 }));
|
|
assert.equal(h.note, note);
|
|
assert.ok(h.button('Repetir o registo original'));
|
|
assert.equal(h.leave(), true);
|
|
});
|
|
test('an older receipt does not silently adopt a later team decision', async () => {
|
|
const h = harness(),
|
|
request = prepare(h);
|
|
await h.fail();
|
|
h.click('Actualizar incidente');
|
|
h.refreshed(
|
|
next(h, request, {
|
|
version: 3,
|
|
status: 'resolved',
|
|
assignee_name: 'SYNTHETIC colleague',
|
|
}),
|
|
);
|
|
assert.equal(h.note, '');
|
|
assert.match(text(h.tree), /O incidente mudou/);
|
|
assert.equal(h.button('Acrescentar nota').props.disabled, true);
|
|
h.click('Confirmar leitura do estado actual');
|
|
h.change(note);
|
|
assert.equal(h.click('Acrescentar nota').data.version, 3);
|
|
});
|
|
test('stale props require explicit comparison and preserve the prepared note', () => {
|
|
const h = harness();
|
|
h.change(note);
|
|
h.render({
|
|
...h.props,
|
|
incident: {
|
|
...h.props.incident,
|
|
handling: {
|
|
...h.props.incident.handling,
|
|
version: 1,
|
|
status: 'in_progress',
|
|
assignee_name: 'SYNTHETIC colleague',
|
|
},
|
|
},
|
|
});
|
|
h.click('Resolver incidente');
|
|
assert.equal(h.sent.length, 0);
|
|
h.click('Confirmar leitura do estado actual');
|
|
assert.equal(h.click('Resolver incidente').data.version, 1);
|
|
assert.equal(h.note, note);
|
|
});
|
|
test('a conflicting command is abandoned only after a newer authorised state is explicitly adopted', async () => {
|
|
const h = harness(),
|
|
request = prepare(h);
|
|
await h.fail(409);
|
|
h.click('Actualizar incidente');
|
|
h.refreshed({
|
|
...h.props,
|
|
incident: {
|
|
...h.props.incident,
|
|
handling: {
|
|
...h.props.incident.handling,
|
|
version: 2,
|
|
status: 'resolved',
|
|
},
|
|
},
|
|
});
|
|
assert.equal(h.sent.length, 1);
|
|
h.click('Confirmar leitura do estado actual');
|
|
const retry = h.click('Reabrir incidente');
|
|
assert.equal(retry.data.version, 2);
|
|
assert.notEqual(retry.data.operation_key, request.data.operation_key);
|
|
assert.equal(retry.data.note, note);
|
|
});
|
|
test('a read at the same version cannot discard a still uncertain command', async () => {
|
|
const h = harness(),
|
|
request = prepare(h);
|
|
await h.fail();
|
|
h.click('Actualizar incidente');
|
|
h.refreshed({ ...h.props });
|
|
assert.equal(h.button('Confirmar leitura do estado actual'), undefined);
|
|
assert.deepEqual(h.click('Repetir o registo original').data, request.data);
|
|
});
|
|
test('a definitive validation rejection preserves text and permits a corrected new command', async () => {
|
|
const h = harness(),
|
|
request = prepare(h);
|
|
await h.fail(422);
|
|
assert.equal(h.note, note);
|
|
assert.match(text(h.tree), /SYNTHETIC rejected note/);
|
|
h.change('PRIVATE SYNTHETIC corrected note');
|
|
const fresh = h.click('Acrescentar nota');
|
|
assert.notEqual(fresh.data.operation_key, request.data.operation_key);
|
|
assert.equal(fresh.data.version, 0);
|
|
});
|
|
test('access loss preserves the original attempt until a fresh authorised read', async () => {
|
|
const h = harness(),
|
|
request = prepare(h);
|
|
await h.fail(403);
|
|
assert.equal(h.button('Repetir o registo original'), undefined);
|
|
assert.equal(h.note, note);
|
|
h.click('Actualizar incidente');
|
|
h.refreshed({ ...h.props });
|
|
assert.deepEqual(h.click('Repetir o registo original').data, request.data);
|
|
});
|
|
test('late callbacks from another account cannot acknowledge or overwrite its note', async () => {
|
|
const h = harness(),
|
|
request = prepare(h);
|
|
h.render({ ...h.props, actorId: 2 });
|
|
assert.equal(h.note, '');
|
|
h.change('PRIVATE SYNTHETIC next account note');
|
|
await h.reply(result(h, request, { actor_id: 1 }), request);
|
|
assert.equal(h.note, 'PRIVATE SYNTHETIC next account note');
|
|
assert.equal(h.sent.length, 1);
|
|
});
|
|
test('a late response to an already confirmed command cannot clear or unlock the next request', async () => {
|
|
const h = harness(),
|
|
first = prepare(h);
|
|
h.click('Actualizar incidente');
|
|
h.refreshed(next(h, first));
|
|
h.change('PRIVATE SYNTHETIC next command note');
|
|
const second = h.click('Acrescentar nota');
|
|
await h.reply(result(h, first), first);
|
|
assert.equal(h.note, 'PRIVATE SYNTHETIC next command note');
|
|
assert.equal(h.button('Acrescentar nota').props.disabled, true);
|
|
assert.equal(h.sent.length, 2);
|
|
await h.reply(result(h, second), second);
|
|
assert.equal(h.note, '');
|
|
});
|
|
test('the detail uses one leave guard and keeps unsent text through history refresh', () => {
|
|
const h = harness();
|
|
h.change(note);
|
|
assert.equal(h.leave(), true);
|
|
assert.equal(h.confirmCalls, 1);
|
|
assert.equal(h.leave(true), false);
|
|
h.click('Actualizar incidente');
|
|
h.refreshed({
|
|
...h.props,
|
|
history: {
|
|
...h.props.history,
|
|
data: [
|
|
{
|
|
id: 'one',
|
|
version: 1,
|
|
action: 'note',
|
|
status: 'open',
|
|
actor: 'SYNTHETIC colleague',
|
|
occurred_at: '2026-11-03',
|
|
note: 'SYNTHETIC previous treatment',
|
|
note_available: true,
|
|
},
|
|
],
|
|
},
|
|
});
|
|
assert.equal(h.note, note);
|
|
assert.match(text(h.tree), /SYNTHETIC previous treatment/);
|
|
});
|