Keep the account submit lock independent of modal sessions, use the exact button labels, and separate persistence from renderer application so a post-save UI failure cannot invite a duplicate save.
209 lines
5.8 KiB
JavaScript
209 lines
5.8 KiB
JavaScript
const { test } = require('node:test');
|
|
const assert = require('node:assert/strict');
|
|
const {
|
|
createAccountSubmitter,
|
|
getAccountSubmitLabel,
|
|
submitValidatedAccount
|
|
} = require('../renderer/account-submit');
|
|
|
|
test('account submit labels stay exact for add, edit, and OTP retries', () => {
|
|
assert.equal(getAccountSubmitLabel({ isEdit: false, hasOtp: false }), 'Prüfen und anlegen');
|
|
assert.equal(getAccountSubmitLabel({ isEdit: true, hasOtp: false }), 'Prüfen und speichern');
|
|
assert.equal(getAccountSubmitLabel({ isEdit: false, hasOtp: true }), 'Prüfen und anlegen');
|
|
assert.equal(getAccountSubmitLabel({ isEdit: true, hasOtp: true }), 'Prüfen und speichern');
|
|
});
|
|
|
|
test('close and reopen cannot start a second save while the first save is pending', async () => {
|
|
const submitter = createAccountSubmitter();
|
|
let current = true;
|
|
let commits = 0;
|
|
let applies = 0;
|
|
let saveStarted;
|
|
let finishSave;
|
|
const started = new Promise(resolve => { saveStarted = resolve; });
|
|
const saving = new Promise(resolve => { finishSave = resolve; });
|
|
const first = submitter.submit({
|
|
validate: async () => ({ status: 'ok' }),
|
|
commit: async () => {
|
|
commits++;
|
|
saveStarted();
|
|
await saving;
|
|
return { accountId: 'first' };
|
|
},
|
|
afterCommit: async () => {
|
|
applies++;
|
|
},
|
|
isCurrent: () => current
|
|
});
|
|
|
|
await started;
|
|
current = false;
|
|
const second = submitter.submit({
|
|
validate: async () => ({ status: 'ok' }),
|
|
commit: async () => {
|
|
commits++;
|
|
},
|
|
isCurrent: () => true
|
|
});
|
|
|
|
assert.equal(second, null);
|
|
assert.equal(submitter.isBusy(), true);
|
|
finishSave();
|
|
const result = await first;
|
|
|
|
assert.equal(result.status, 'stale');
|
|
assert.equal(result.committed, true);
|
|
assert.equal(commits, 1);
|
|
assert.equal(applies, 1);
|
|
assert.equal(submitter.isBusy(), false);
|
|
});
|
|
|
|
test('post-save apply failure remains committed and cannot invite a duplicate retry', async () => {
|
|
const expected = new Error('render failed');
|
|
let saves = 0;
|
|
let applies = 0;
|
|
const result = await submitValidatedAccount({
|
|
validate: async () => ({ status: 'ok' }),
|
|
commit: async () => {
|
|
saves++;
|
|
return { accountId: 'saved-account' };
|
|
},
|
|
afterCommit: async () => {
|
|
applies++;
|
|
throw expected;
|
|
},
|
|
isCurrent: () => true
|
|
});
|
|
|
|
assert.equal(result.status, 'committed');
|
|
assert.equal(result.value.accountId, 'saved-account');
|
|
assert.equal(result.postCommitError, expected);
|
|
assert.equal(saves, 1);
|
|
assert.equal(applies, 1);
|
|
});
|
|
|
|
test('ok validates and commits exactly once in one submission', async () => {
|
|
let validations = 0;
|
|
let commits = 0;
|
|
const result = await submitValidatedAccount({
|
|
validate: async () => {
|
|
validations++;
|
|
return { status: 'ok', message: 'Login erfolgreich' };
|
|
},
|
|
commit: async () => {
|
|
commits++;
|
|
},
|
|
isCurrent: () => true
|
|
});
|
|
|
|
assert.equal(result.status, 'committed');
|
|
assert.equal(validations, 1);
|
|
assert.equal(commits, 1);
|
|
});
|
|
|
|
test('warn validates and commits exactly once in one submission', async () => {
|
|
let commits = 0;
|
|
const validation = { status: 'warn', message: 'Login mit Warnung' };
|
|
const result = await submitValidatedAccount({
|
|
validate: async () => validation,
|
|
commit: async (received) => {
|
|
commits++;
|
|
assert.equal(received, validation);
|
|
},
|
|
isCurrent: () => true
|
|
});
|
|
|
|
assert.equal(result.status, 'committed');
|
|
assert.equal(result.validation, validation);
|
|
assert.equal(commits, 1);
|
|
});
|
|
|
|
for (const status of ['error', 'skipped']) {
|
|
test(`${status} rejects without committing`, async () => {
|
|
let commits = 0;
|
|
const validation = { status, message: `${status} result` };
|
|
const result = await submitValidatedAccount({
|
|
validate: async () => validation,
|
|
commit: async () => {
|
|
commits++;
|
|
},
|
|
isCurrent: () => true
|
|
});
|
|
|
|
assert.equal(result.status, 'rejected');
|
|
assert.equal(result.validation, validation);
|
|
assert.equal(commits, 0);
|
|
});
|
|
}
|
|
|
|
test('validate throw returns error without committing', async () => {
|
|
const expected = new Error('validation failed');
|
|
let commits = 0;
|
|
const result = await submitValidatedAccount({
|
|
validate: async () => {
|
|
throw expected;
|
|
},
|
|
commit: async () => {
|
|
commits++;
|
|
},
|
|
isCurrent: () => true
|
|
});
|
|
|
|
assert.equal(result.status, 'error');
|
|
assert.equal(result.error, expected);
|
|
assert.equal(commits, 0);
|
|
});
|
|
|
|
test('otp_required returns challenge without committing', async () => {
|
|
let commits = 0;
|
|
const validation = { status: 'otp_required', message: 'OTP gesendet' };
|
|
const result = await submitValidatedAccount({
|
|
validate: async () => validation,
|
|
commit: async () => {
|
|
commits++;
|
|
},
|
|
isCurrent: () => true
|
|
});
|
|
|
|
assert.equal(result.status, 'otp_required');
|
|
assert.equal(result.validation, validation);
|
|
assert.equal(commits, 0);
|
|
});
|
|
|
|
test('stale submission is rejected immediately before commit', async () => {
|
|
let current = true;
|
|
let commits = 0;
|
|
const validation = { status: 'ok' };
|
|
const result = await submitValidatedAccount({
|
|
validate: async () => {
|
|
current = false;
|
|
return validation;
|
|
},
|
|
commit: async () => {
|
|
commits++;
|
|
},
|
|
isCurrent: () => current
|
|
});
|
|
|
|
assert.equal(result.status, 'stale');
|
|
assert.equal(result.validation, validation);
|
|
assert.equal(commits, 0);
|
|
});
|
|
|
|
test('save failure returns error after one commit attempt', async () => {
|
|
const expected = new Error('save failed');
|
|
let commits = 0;
|
|
const result = await submitValidatedAccount({
|
|
validate: async () => ({ status: 'ok' }),
|
|
commit: async () => {
|
|
commits++;
|
|
throw expected;
|
|
},
|
|
isCurrent: () => true
|
|
});
|
|
|
|
assert.equal(result.status, 'error');
|
|
assert.equal(result.error, expected);
|
|
assert.equal(commits, 1);
|
|
});
|