258 lines
9.1 KiB
JavaScript
258 lines
9.1 KiB
JavaScript
import test from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import { harness, props, text } from './helpers/partnership-room.mjs';
|
|
test('an uncertain agreement action exposes recovery of the frozen attempt', () => {
|
|
const h = harness(props());
|
|
h.change('agreement', 'Motivo da ação', 'SYNTHETIC reason');
|
|
const request = h.click('Confirmar pela minha parte');
|
|
h.fail(request);
|
|
assert.ok(h.button('Recuperar a mesma acção'));
|
|
});
|
|
|
|
function ack(h, request, patch = {}) {
|
|
const command = request.data;
|
|
const statuses = {
|
|
record: 'agreement_recorded',
|
|
confirm: 'agreement_recorded',
|
|
start: 'execution',
|
|
complete: 'closed',
|
|
cancel: 'cancelled',
|
|
renew: 'negotiation',
|
|
};
|
|
const agreement = { ...h.props.room.agreement };
|
|
if (command.action === 'record') {
|
|
agreement.title = command.title.trim();
|
|
agreement.terms = command.terms.trim();
|
|
agreement.confirmations = {};
|
|
} else if (command.action === 'confirm') {
|
|
agreement.confirmations = {
|
|
...agreement.confirmations,
|
|
[h.props.agreementParty]: {
|
|
actor_id: h.props.agreementActorId,
|
|
date: '2026-11-03',
|
|
reason: command.reason.trim(),
|
|
},
|
|
};
|
|
}
|
|
return {
|
|
...h.props,
|
|
room: {
|
|
...h.props.room,
|
|
agreement,
|
|
version: command.version + 1,
|
|
status: statuses[command.action],
|
|
...patch,
|
|
},
|
|
agreementCommands: [
|
|
{
|
|
key: command.client_key,
|
|
action: command.action,
|
|
version: command.version + 1,
|
|
status: patch.status ?? statuses[command.action],
|
|
party:
|
|
command.action === 'confirm'
|
|
? h.props.agreementParty
|
|
: null,
|
|
},
|
|
],
|
|
};
|
|
}
|
|
const adopt = 'Usar este contexto e conservar as alterações escolhidas';
|
|
const recover = 'Recuperar a mesma acção';
|
|
const refresh = 'Comparar com os dados actuais da sala';
|
|
function prepared() {
|
|
const h = harness(props());
|
|
h.change('agreement', 'Motivo da ação', 'SYNTHETIC decision');
|
|
return h;
|
|
}
|
|
|
|
test('confirmation recovery freezes context and reason while preserving later preparation', () => {
|
|
const h = prepared();
|
|
h.change(
|
|
'agreement',
|
|
'Título do instrumento',
|
|
'SYNTHETIC unrecorded title',
|
|
);
|
|
const first = h.click('Confirmar pela minha parte');
|
|
const frozen = { ...first.data };
|
|
assert.equal(first.data.partnership_version, 4);
|
|
assert.equal(first.data.interest_version, 3);
|
|
assert.equal(first.data.title, undefined);
|
|
h.fail(first);
|
|
h.change('agreement', 'Motivo da ação', 'SYNTHETIC next reason');
|
|
const repeated = h.click(recover);
|
|
assert.deepEqual(repeated.data, frozen);
|
|
h.success(ack(h, repeated), repeated);
|
|
assert.equal(
|
|
h.field('agreement', 'Motivo da ação').props.value,
|
|
'SYNTHETIC next reason',
|
|
);
|
|
assert.equal(
|
|
h.field('agreement', 'Título do instrumento').props.value,
|
|
'SYNTHETIC unrecorded title',
|
|
);
|
|
assert.equal(h.button(recover), undefined);
|
|
});
|
|
|
|
test('record recovery preserves terms written after the original command', () => {
|
|
const h = prepared();
|
|
h.change(
|
|
'agreement',
|
|
'Condições e plano de valor',
|
|
'SYNTHETIC submitted terms',
|
|
);
|
|
const first = h.click('Registar versão do acordo');
|
|
h.fail(first);
|
|
h.change('agreement', 'Condições e plano de valor', 'SYNTHETIC next terms');
|
|
const repeated = h.click(recover);
|
|
assert.equal(repeated.data.terms, 'SYNTHETIC submitted terms');
|
|
h.success(ack(h, repeated), repeated);
|
|
assert.equal(
|
|
h.field('agreement', 'Condições e plano de valor').props.value,
|
|
'SYNTHETIC next terms',
|
|
);
|
|
assert.equal(h.field('agreement', 'Motivo da ação').props.value, '');
|
|
});
|
|
|
|
test('a terminal cancellation still permits recovery of the same command', () => {
|
|
const h = prepared();
|
|
const first = h.click('Cancelar conexão com motivo');
|
|
h.fail(first);
|
|
const terminal = ack(h, first);
|
|
h.render(terminal);
|
|
assert.equal(h.button(recover).props.disabled, false);
|
|
const repeated = h.click(recover);
|
|
assert.deepEqual(repeated.data, first.data);
|
|
h.success(terminal, repeated);
|
|
assert.equal(h.button(recover), undefined);
|
|
});
|
|
|
|
test('a fresh read can acknowledge a committed attempt without another write', () => {
|
|
const h = prepared();
|
|
const first = h.click('Confirmar pela minha parte');
|
|
h.fail(first);
|
|
h.click(refresh);
|
|
h.refreshed(ack(h, first));
|
|
assert.equal(h.sent.length, 1);
|
|
assert.equal(h.button(recover), undefined);
|
|
assert.equal(h.field('agreement', 'Motivo da ação').props.value, '');
|
|
});
|
|
|
|
test('a recovered command followed by a bilateral decision requires explicit review', () => {
|
|
const h = prepared();
|
|
const first = h.click('Confirmar pela minha parte');
|
|
h.fail(first);
|
|
const repeated = h.click(recover);
|
|
const later = ack(h, repeated, {
|
|
version: first.data.version + 2,
|
|
status: 'agreed',
|
|
});
|
|
later.room.agreement.confirmations.candidate = {
|
|
actor_id: 2,
|
|
date: '2026-11-03',
|
|
reason: 'SYNTHETIC other decision',
|
|
};
|
|
later.agreementCommands[0].status = 'agreement_recorded';
|
|
h.success(later, repeated);
|
|
assert.ok(h.button(adopt));
|
|
assert.equal(h.button('Iniciar acompanhamento').props.disabled, true);
|
|
h.click(adopt);
|
|
h.change('agreement', 'Motivo da ação', 'SYNTHETIC start after review');
|
|
assert.equal(h.button('Iniciar acompanhamento').props.disabled, false);
|
|
});
|
|
|
|
test('a changed proposal with the same room version requires context adoption', () => {
|
|
const h = prepared();
|
|
h.render({
|
|
...h.props,
|
|
partnership: { ...h.props.partnership, version: 5 },
|
|
});
|
|
assert.equal(h.button('Confirmar pela minha parte').props.disabled, true);
|
|
h.click(adopt);
|
|
const request = h.click('Confirmar pela minha parte');
|
|
assert.equal(request.data.partnership_version, 5);
|
|
assert.equal(request.data.version, 2);
|
|
});
|
|
|
|
test('an exact command receipt with a newer parent context still requires review', () => {
|
|
const h = prepared();
|
|
const first = h.click('Confirmar pela minha parte');
|
|
const next = ack(h, first);
|
|
next.interest = { ...next.interest, version: 4 };
|
|
h.success(next, first);
|
|
assert.ok(h.button(adopt));
|
|
assert.equal(
|
|
h.button('Actualizar confirmação da minha parte').props.disabled,
|
|
true,
|
|
);
|
|
h.click(adopt);
|
|
h.change('agreement', 'Motivo da ação', 'SYNTHETIC updated context');
|
|
assert.equal(
|
|
h.button('Actualizar confirmação da minha parte').props.disabled,
|
|
false,
|
|
);
|
|
});
|
|
|
|
test('missing own receipt or an incompatible confirmation stamp never clears a draft', () => {
|
|
for (const wrong of ['receipt', 'stamp']) {
|
|
const h = prepared();
|
|
const first = h.click('Confirmar pela minha parte');
|
|
const next = ack(h, first);
|
|
if (wrong === 'receipt') next.agreementCommands = [];
|
|
else next.room.agreement.confirmations.proponent.actor_id = 9;
|
|
h.success(next, first);
|
|
assert.equal(
|
|
h.field('agreement', 'Motivo da ação').props.value,
|
|
'SYNTHETIC decision',
|
|
);
|
|
assert.ok(h.button(recover));
|
|
assert.equal(h.button(adopt).props.disabled, true);
|
|
}
|
|
});
|
|
|
|
test('lost write authority blocks recovery and another actor does not inherit its draft', () => {
|
|
const h = prepared();
|
|
const first = h.click('Confirmar pela minha parte');
|
|
h.fail(first);
|
|
h.fail(first, 403);
|
|
assert.equal(h.button(recover).props.disabled, true);
|
|
h.render({
|
|
...h.props,
|
|
actorId: 2,
|
|
agreementActorId: 2,
|
|
agreementParty: 'candidate',
|
|
agreementCommands: [],
|
|
});
|
|
h.success(ack(h, first), first);
|
|
assert.equal(h.field('agreement', 'Motivo da ação').props.value, '');
|
|
assert.equal(h.button(recover), undefined);
|
|
});
|
|
|
|
test('a current confirmation is identified as an update rather than another party', () => {
|
|
const initial = props();
|
|
initial.room.agreement.confirmations.proponent = {
|
|
actor_id: 1,
|
|
reason: 'SYNTHETIC old',
|
|
date: '2026-11-02',
|
|
};
|
|
const h = harness(initial);
|
|
assert.ok(h.button('Actualizar confirmação da minha parte'));
|
|
assert.equal(h.button('Confirmar pela minha parte'), undefined);
|
|
assert.match(text(h.tree), /proponente/i);
|
|
});
|
|
|
|
test('a callback from an abandoned attempt does not freeze its replacement', () => {
|
|
const h = prepared();
|
|
const first = h.click('Confirmar pela minha parte');
|
|
h.fail(first);
|
|
h.click(refresh);
|
|
h.refreshed(h.props);
|
|
h.click(adopt);
|
|
const second = h.click('Confirmar pela minha parte');
|
|
assert.notEqual(second.data.client_key, first.data.client_key);
|
|
h.fail(first);
|
|
h.success(ack(h, second), second);
|
|
assert.equal(h.button(recover), undefined);
|
|
assert.equal(h.field('agreement', 'Motivo da ação').props.value, '');
|
|
});
|