mirror of
https://github.com/coollabsio/coolify.git
synced 2026-11-12 15:53:46 -04:00
Read the running version from the container that answers the status request, not from the old page state, so the modal shows success as soon as the new version is up. Hide all Livewire error pages while an upgrade runs. Closes #8024 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
158 lines
4.6 KiB
JavaScript
158 lines
4.6 KiB
JavaScript
import test from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import {
|
|
GESTURE_WINDOW_MS,
|
|
INFRASTRUCTURE_FAILURE_STATUSES,
|
|
createLivewireRequestFailureHandler,
|
|
registerLivewireRequestFailureHandler,
|
|
} from './livewire-request-failure.js';
|
|
|
|
function createHarness({ start = 100_000 } = {}) {
|
|
let currentTime = start;
|
|
let requestHook = null;
|
|
let toasts = 0;
|
|
let warnings = [];
|
|
const listeners = {};
|
|
|
|
global.window = { toast: () => toasts++ };
|
|
global.console = { warn: (...args) => warnings.push(args) };
|
|
|
|
registerLivewireRequestFailureHandler({
|
|
hook(name, callback) {
|
|
assert.equal(name, 'request');
|
|
requestHook = callback;
|
|
},
|
|
}, {
|
|
addEventListener(name, callback) {
|
|
listeners[name] = callback;
|
|
},
|
|
}, { now: () => currentTime });
|
|
|
|
return {
|
|
listeners,
|
|
advance: (ms) => currentTime += ms,
|
|
gesture: (event = { isTrusted: true }) => listeners.click(event),
|
|
fail(status) {
|
|
let prevented = false;
|
|
let failureCallback = null;
|
|
requestHook({ fail: (callback) => failureCallback = callback });
|
|
failureCallback({ status, content: '<html>proxy error</html>', preventDefault: () => prevented = true });
|
|
return prevented;
|
|
},
|
|
toasts: () => toasts,
|
|
warnings: () => warnings,
|
|
};
|
|
}
|
|
|
|
test('a failure after a trusted gesture suppresses the response and shows a toast', () => {
|
|
const harness = createHarness();
|
|
|
|
harness.gesture();
|
|
const prevented = harness.fail(504);
|
|
|
|
assert.equal(prevented, true);
|
|
assert.equal(harness.toasts(), 1);
|
|
});
|
|
|
|
test('background failures are suppressed and logged without a toast', () => {
|
|
const harness = createHarness();
|
|
|
|
const prevented = harness.fail(524);
|
|
|
|
assert.equal(prevented, true);
|
|
assert.equal(harness.toasts(), 0);
|
|
assert.equal(harness.warnings().length, 1);
|
|
assert.deepEqual(harness.warnings()[0], ['Livewire request failed', {
|
|
status: 524,
|
|
content: '<html>proxy error</html>',
|
|
}]);
|
|
});
|
|
|
|
test('one gesture toasts once, but a retry gesture toasts again', () => {
|
|
const harness = createHarness();
|
|
|
|
harness.gesture();
|
|
harness.fail(504);
|
|
harness.fail(504);
|
|
assert.equal(harness.toasts(), 1);
|
|
|
|
harness.advance(8_000);
|
|
harness.gesture();
|
|
harness.fail(504);
|
|
assert.equal(harness.toasts(), 2);
|
|
});
|
|
|
|
test('requests sent outside the gesture window count as background', () => {
|
|
const harness = createHarness();
|
|
|
|
harness.gesture();
|
|
harness.advance(GESTURE_WINDOW_MS + 1);
|
|
harness.fail(504);
|
|
|
|
assert.equal(harness.toasts(), 0);
|
|
});
|
|
|
|
test('untrusted synthetic events do not count as gestures', () => {
|
|
const harness = createHarness();
|
|
|
|
harness.gesture({ isTrusted: false });
|
|
harness.fail(504);
|
|
|
|
assert.equal(harness.toasts(), 0);
|
|
});
|
|
|
|
test('a missing window.toast does not throw', () => {
|
|
const harness = createHarness();
|
|
delete global.window.toast;
|
|
|
|
harness.gesture();
|
|
assert.doesNotThrow(() => harness.fail(504));
|
|
});
|
|
|
|
test('console warnings are throttled and truncated', () => {
|
|
const harness = createHarness();
|
|
|
|
harness.fail(502);
|
|
harness.fail(504);
|
|
assert.equal(harness.warnings().length, 1);
|
|
|
|
harness.advance(10_000);
|
|
harness.fail(504);
|
|
assert.equal(harness.warnings().length, 2);
|
|
|
|
const handler = createLivewireRequestFailureHandler({ now: () => 0 });
|
|
let logged = null;
|
|
global.console = { warn: (message, details) => logged = details };
|
|
handler({ status: 502, content: 'x'.repeat(5_000), preventDefault() {} });
|
|
assert.equal(logged.content.length, 2_000);
|
|
});
|
|
|
|
test('all supported infrastructure status codes are handled', () => {
|
|
const harness = createHarness();
|
|
|
|
for (const status of INFRASTRUCTURE_FAILURE_STATUSES) {
|
|
assert.equal(harness.fail(status), true, `expected ${status} to be handled`);
|
|
}
|
|
});
|
|
|
|
test('other failures keep Livewire default handling', () => {
|
|
const harness = createHarness();
|
|
|
|
harness.gesture();
|
|
for (const status of [401, 419, 422, 429, 500]) {
|
|
assert.equal(harness.fail(status), false, `expected ${status} to be untouched`);
|
|
}
|
|
assert.equal(harness.toasts(), 0);
|
|
});
|
|
|
|
test('during a Coolify upgrade every failure is suppressed without a toast', () => {
|
|
const harness = createHarness();
|
|
global.window.coolifyUpgradeInProgress = true;
|
|
|
|
harness.gesture();
|
|
for (const status of [404, 419, 500, 502]) {
|
|
assert.equal(harness.fail(status), true, `expected ${status} to be suppressed`);
|
|
}
|
|
assert.equal(harness.toasts(), 0);
|
|
});
|