Display session playback error (#3280)
This commit is contained in:
1 parent
9b43819865
commit
6eb21d3e13
11 files changed
+432
-7
No files matched your search
@@ -96,19 +96,19 @@ export default class GeneratedSessionRecordingModel extends BaseModel {
|
|||||||
})
|
})
|
||||||
duration;
|
duration;
|
||||||
|
|
||||||
@attr('string', {
|
@attr({
|
||||||
description:
|
description:
|
||||||
'The current state of the session recording. One of "started", "available" and "unknown".',
|
'The detailed state of the session recording. This contains storage_state, syncing_error_details, and verification_error_details.',
|
||||||
readOnly: true,
|
readOnly: true,
|
||||||
})
|
})
|
||||||
state;
|
recording_state;
|
||||||
|
|
||||||
@attr('string', {
|
@attr('string', {
|
||||||
description:
|
description:
|
||||||
'Any error seen during the closing of the session recording. Currently only set if state is "unknown".',
|
'The availability of the session recording. One of "available" or "not available".',
|
||||||
readOnly: true,
|
readOnly: true,
|
||||||
})
|
})
|
||||||
error_details;
|
availability;
|
||||||
|
|
||||||
@attr('date', {
|
@attr('date', {
|
||||||
description: 'The time until a session recording is required to be stored.',
|
description: 'The time until a session recording is required to be stored.',
|
||||||
@@ -122,4 +122,19 @@ export default class GeneratedSessionRecordingModel extends BaseModel {
|
|||||||
readOnly: true,
|
readOnly: true,
|
||||||
})
|
})
|
||||||
delete_after;
|
delete_after;
|
||||||
|
|
||||||
|
// Deprecated: These two fields should be removed once support for RDP is added.
|
||||||
|
@attr('string', {
|
||||||
|
description:
|
||||||
|
'The current state of the session recording. One of "started", "available" and "unknown".',
|
||||||
|
readOnly: true,
|
||||||
|
})
|
||||||
|
state;
|
||||||
|
|
||||||
|
@attr('string', {
|
||||||
|
description:
|
||||||
|
'Any error seen during the closing of the session recording. Currently only set if state is "unknown".',
|
||||||
|
readOnly: true,
|
||||||
|
})
|
||||||
|
error_details;
|
||||||
}
|
}
|
||||||
@@ -10,8 +10,8 @@ import { faker } from '@faker-js/faker';
|
|||||||
import { trait } from 'miragejs';
|
import { trait } from 'miragejs';
|
||||||
import {
|
import {
|
||||||
TYPES_SESSION_RECORDING as types,
|
TYPES_SESSION_RECORDING as types,
|
||||||
STATE_SESSION_RECORDING_STARTED,
|
|
||||||
STATE_SESSION_RECORDING_AVAILABLE,
|
STATE_SESSION_RECORDING_AVAILABLE,
|
||||||
|
STATE_SESSION_RECORDING_STARTED,
|
||||||
STATE_SESSION_RECORDING_UNKNOWN,
|
STATE_SESSION_RECORDING_UNKNOWN,
|
||||||
} from 'api/models/session-recording';
|
} from 'api/models/session-recording';
|
||||||
|
|
||||||
@@ -27,6 +27,29 @@ export default factory.extend({
|
|||||||
// Cycle through available types
|
// Cycle through available types
|
||||||
type: (i) => types[i % types.length],
|
type: (i) => types[i % types.length],
|
||||||
state: (i) => states[i % states.length],
|
state: (i) => states[i % states.length],
|
||||||
|
|
||||||
|
recording_state(i) {
|
||||||
|
// Only return error details if state is 'unknown' (failed)
|
||||||
|
if (this.state !== STATE_SESSION_RECORDING_UNKNOWN) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
const errorScenarios = [
|
||||||
|
{
|
||||||
|
syncing_error_details:
|
||||||
|
'sync errors in container "sr_jovU2XLzBP.bsr": file "sr_jovU2XLzBP.bsr/session-recording.meta": storage.(syncingFile).sync: storage.parseStoragePluginError: plugin error, external system issue: error #3001: rpc error: \ncode = Internal desc = failed to put object into minio: Get "http://minio:9000/my-special-storage-bucket/?location=": dial tcp: lookup minio on 10.89.0.1:53: no such host',
|
||||||
|
verification_error_details:
|
||||||
|
'integrity check failed for session recording file "sr_jovU2XLzBP.bsr/channels/connection-1/channel-recording-data.bin": expected SHA-256 checksum "a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0u1v2w3x4y5z6" but computed \nchecksum was "z6y5x4w3v2u1t0s9r8q7p6o5n4m3l2k1j0i9h8g7f6e5d4c3b2a1" indicating potential data corruption during transfer or storage',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
syncing_error_details:
|
||||||
|
'sync errors in container "sr_jovU2XLzBP.bsr": file "sr_jovU2XLzBP.bsr/session-recording.meta": storage.(syncingFile).sync: storage.parseStoragePluginError: plugin error, external system issue: error #3001: rpc error: code = Internal desc = failed to put object into minio: Get "http://minio:9000/my-special-storage-bucket/?location=": dial tcp: lookup minio on 10.89.0.1:53: no such host',
|
||||||
|
verification_error_details: null,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
return errorScenarios[i % 2];
|
||||||
|
},
|
||||||
|
|
||||||
start_time() {
|
start_time() {
|
||||||
return this.state === STATE_SESSION_RECORDING_AVAILABLE
|
return this.state === STATE_SESSION_RECORDING_AVAILABLE
|
||||||
? faker.date.recent({ days: 3, refDate: this.end_time })
|
? faker.date.recent({ days: 3, refDate: this.end_time })
|
||||||
|
|||||||
@@ -71,3 +71,4 @@ edit-worker-filter: Edit Worker Filter
|
|||||||
add-worker-filter: Add Worker Filter
|
add-worker-filter: Add Worker Filter
|
||||||
open: Open
|
open: Open
|
||||||
download: Download
|
download: Download
|
||||||
|
show-more: Show more
|
||||||
@@ -420,6 +420,13 @@ session-recording:
|
|||||||
title: Channels by connection
|
title: Channels by connection
|
||||||
messages:
|
messages:
|
||||||
no-channels: There are no channels for this connection
|
no-channels: There are no channels for this connection
|
||||||
|
flyout:
|
||||||
|
title: Session playback errors
|
||||||
|
sync-error:
|
||||||
|
title: This recording is not available
|
||||||
|
flyout-section-title: Sync error
|
||||||
|
verification-error:
|
||||||
|
flyout-section-title: Verification error
|
||||||
started-no-connections:
|
started-no-connections:
|
||||||
title: Session Recording In Progress
|
title: Session Recording In Progress
|
||||||
description: The Session has started but no connections have been made.
|
description: The Session has started but no connections have been made.
|
||||||
|
|||||||
@@ -0,0 +1,107 @@
|
|||||||
|
{{!
|
||||||
|
Copyright IBM Corp. 2021, 2026
|
||||||
|
SPDX-License-Identifier: BUSL-1.1
|
||||||
|
}}
|
||||||
|
{{#if (or @syncError @verifyError)}}
|
||||||
|
<Hds::Alert
|
||||||
|
data-test-session-playback-alert
|
||||||
|
@type='inline'
|
||||||
|
@color='critical'
|
||||||
|
as |A|
|
||||||
|
>
|
||||||
|
<A.Title>{{t
|
||||||
|
'resources.session-recording.channels-by-connection.messages.sync-error.title'
|
||||||
|
}}</A.Title>
|
||||||
|
<A.Description>
|
||||||
|
{{#if (and @syncError @verifyError)}}
|
||||||
|
<ul>
|
||||||
|
<li class='playback-error-item margin-bottom-s'>
|
||||||
|
<Hds::Text::Body @tag='p' class='playback-error-message'>
|
||||||
|
{{@syncError}}
|
||||||
|
</Hds::Text::Body>
|
||||||
|
</li>
|
||||||
|
<li class='playback-error-item'>
|
||||||
|
<Hds::Text::Body @tag='p' class='playback-error-message'>
|
||||||
|
{{@verifyError}}
|
||||||
|
</Hds::Text::Body>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
{{else}}
|
||||||
|
<Hds::Text::Body @tag='p' class='playback-error-message'>
|
||||||
|
{{or @syncError @verifyError}}
|
||||||
|
</Hds::Text::Body>
|
||||||
|
{{/if}}
|
||||||
|
</A.Description>
|
||||||
|
<A.Button
|
||||||
|
@text={{t 'actions.show-more'}}
|
||||||
|
@color='secondary'
|
||||||
|
{{on 'click' @onShowFlyout}}
|
||||||
|
/>
|
||||||
|
</Hds::Alert>
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
|
{{#if @showFlyout}}
|
||||||
|
<Hds::Flyout
|
||||||
|
class='session-playback-error-flyout'
|
||||||
|
@size='large'
|
||||||
|
@onClose={{@onShowFlyout}}
|
||||||
|
data-test-error-flyout
|
||||||
|
as |F|
|
||||||
|
>
|
||||||
|
<F.Header>{{t
|
||||||
|
'resources.session-recording.channels-by-connection.messages.flyout.title'
|
||||||
|
}}</F.Header>
|
||||||
|
<F.Body>
|
||||||
|
<Hds::Accordion @size='large' as |A|>
|
||||||
|
{{#if @syncErrorLines.length}}
|
||||||
|
<A.Item @isOpen={{true}}>
|
||||||
|
<:toggle>
|
||||||
|
<Hds::Text::Body @tag='p' @size='300' @weight='medium'>
|
||||||
|
{{t
|
||||||
|
'resources.session-recording.channels-by-connection.messages.sync-error.flyout-section-title'
|
||||||
|
}}
|
||||||
|
</Hds::Text::Body>
|
||||||
|
<Hds::Copy::Button
|
||||||
|
@size='small'
|
||||||
|
@text={{t 'actions.copy'}}
|
||||||
|
@isIconOnly={{true}}
|
||||||
|
@textToCopy={{@syncError}}
|
||||||
|
/>
|
||||||
|
</:toggle>
|
||||||
|
<:content>
|
||||||
|
{{#each @syncErrorLines as |error|}}
|
||||||
|
<Hds::Text::Body @tag='p' class='flyout-error-message'>
|
||||||
|
{{error}}
|
||||||
|
</Hds::Text::Body>
|
||||||
|
{{/each}}
|
||||||
|
</:content>
|
||||||
|
</A.Item>
|
||||||
|
{{/if}}
|
||||||
|
{{#if @verifyErrorLines.length}}
|
||||||
|
<A.Item @isOpen={{true}}>
|
||||||
|
<:toggle>
|
||||||
|
<Hds::Text::Body @tag='p' @size='300' @weight='medium'>
|
||||||
|
{{t
|
||||||
|
'resources.session-recording.channels-by-connection.messages.verification-error.flyout-section-title'
|
||||||
|
}}
|
||||||
|
</Hds::Text::Body>
|
||||||
|
<Hds::Copy::Button
|
||||||
|
@size='small'
|
||||||
|
@text={{t 'actions.copy'}}
|
||||||
|
@isIconOnly={{true}}
|
||||||
|
@textToCopy={{@verifyError}}
|
||||||
|
/>
|
||||||
|
</:toggle>
|
||||||
|
<:content>
|
||||||
|
{{#each @verifyErrorLines as |error|}}
|
||||||
|
<Hds::Text::Body @tag='p' class='flyout-error-message'>
|
||||||
|
{{error}}
|
||||||
|
</Hds::Text::Body>
|
||||||
|
{{/each}}
|
||||||
|
</:content>
|
||||||
|
</A.Item>
|
||||||
|
{{/if}}
|
||||||
|
</Hds::Accordion>
|
||||||
|
</F.Body>
|
||||||
|
</Hds::Flyout>
|
||||||
|
{{/if}}
|
||||||
+27
@@ -13,12 +13,19 @@ import {
|
|||||||
STATE_SESSION_RECORDING_STARTED,
|
STATE_SESSION_RECORDING_STARTED,
|
||||||
STATE_SESSION_RECORDING_UNKNOWN,
|
STATE_SESSION_RECORDING_UNKNOWN,
|
||||||
} from 'api/models/session-recording';
|
} from 'api/models/session-recording';
|
||||||
|
import { tracked } from '@glimmer/tracking';
|
||||||
|
|
||||||
export default class ScopesScopeSessionRecordingsSessionRecordingChannelsByConnectionIndexController extends Controller {
|
export default class ScopesScopeSessionRecordingsSessionRecordingChannelsByConnectionIndexController extends Controller {
|
||||||
// =services
|
// =services
|
||||||
|
|
||||||
@service router;
|
@service router;
|
||||||
|
|
||||||
|
// =tracked
|
||||||
|
|
||||||
|
@tracked showErrorFlyout = false;
|
||||||
|
@tracked syncErrorLines = [];
|
||||||
|
@tracked verifyErrorLines = [];
|
||||||
|
|
||||||
// =attributes
|
// =attributes
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -87,4 +94,24 @@ export default class ScopesScopeSessionRecordingsSessionRecordingChannelsByConne
|
|||||||
throw new Error(e);
|
throw new Error(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// =actions
|
||||||
|
/**
|
||||||
|
* Toggles the error flyout open and closed, and sets the error messages to be displayed when opening.
|
||||||
|
*/
|
||||||
|
@action
|
||||||
|
toggleErrorFlyout() {
|
||||||
|
if (this.showErrorFlyout) {
|
||||||
|
this.showErrorFlyout = false;
|
||||||
|
} else {
|
||||||
|
const recordingState = this.model.sessionRecording?.recording_state || {};
|
||||||
|
|
||||||
|
const { syncing_error_details, verification_error_details } =
|
||||||
|
recordingState;
|
||||||
|
|
||||||
|
this.syncErrorLines = syncing_error_details?.split('\n') ?? [];
|
||||||
|
this.verifyErrorLines = verification_error_details?.split('\n') ?? [];
|
||||||
|
this.showErrorFlyout = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -748,6 +748,35 @@
|
|||||||
.no-channels-message {
|
.no-channels-message {
|
||||||
padding: 0 1rem;
|
padding: 0 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.playback-error-item {
|
||||||
|
list-style: disc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.playback-error-message {
|
||||||
|
display: block;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* stylelint-disable-next-line selector-class-pattern */
|
||||||
|
.hds-alert__content {
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Session playback error flyout
|
||||||
|
.session-playback-error-flyout {
|
||||||
|
.flyout-error-message {
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
/* stylelint-disable-next-line selector-class-pattern */
|
||||||
|
.hds-accordion-item__toggle-content {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Channel
|
// Channel
|
||||||
|
|||||||
+8
@@ -46,6 +46,14 @@
|
|||||||
<div class='channels-by-connection'>
|
<div class='channels-by-connection'>
|
||||||
<Rose::Layout::BodyContent as |bc|>
|
<Rose::Layout::BodyContent as |bc|>
|
||||||
<bc.Body>
|
<bc.Body>
|
||||||
|
<SessionRecording::ErrorFlyoutAlert
|
||||||
|
@syncError={{@model.sessionRecording.recording_state.syncing_error_details}}
|
||||||
|
@verifyError={{@model.sessionRecording.recording_state.verification_error_details}}
|
||||||
|
@syncErrorLines={{this.syncErrorLines}}
|
||||||
|
@verifyErrorLines={{this.verifyErrorLines}}
|
||||||
|
@showFlyout={{this.showErrorFlyout}}
|
||||||
|
@onShowFlyout={{this.toggleErrorFlyout}}
|
||||||
|
/>
|
||||||
{{#if this.isSessionInprogressWithNoConnections}}
|
{{#if this.isSessionInprogressWithNoConnections}}
|
||||||
<Hds::ApplicationState as |A|>
|
<Hds::ApplicationState as |A|>
|
||||||
{{! Session recording in progress with no connections }}
|
{{! Session recording in progress with no connections }}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { module, test } from 'qunit';
|
import { module, test } from 'qunit';
|
||||||
import { visit, currentURL, click } from '@ember/test-helpers';
|
import { visit, currentURL, click, findAll } from '@ember/test-helpers';
|
||||||
import { setupApplicationTest } from 'admin/tests/helpers';
|
import { setupApplicationTest } from 'admin/tests/helpers';
|
||||||
import { setupIntl } from 'ember-intl/test-support';
|
import { setupIntl } from 'ember-intl/test-support';
|
||||||
import { setupSqlite } from 'api/test-support/helpers/sqlite';
|
import { setupSqlite } from 'api/test-support/helpers/sqlite';
|
||||||
@@ -175,4 +175,70 @@ module('Acceptance | session-recordings | read', function (hooks) {
|
|||||||
assert.notEqual(currentURL(), incorrectUrl);
|
assert.notEqual(currentURL(), incorrectUrl);
|
||||||
assert.strictEqual(currentURL(), urls.sessionRecording);
|
assert.strictEqual(currentURL(), urls.sessionRecording);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('it displays error messages for session recording', async function (assert) {
|
||||||
|
setRunOptions({
|
||||||
|
rules: {
|
||||||
|
// [ember-a11y-ignore]: axe rule "color-contrast" automatically ignored on 2026-05-22
|
||||||
|
'color-contrast': {
|
||||||
|
enabled: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
instances.sessionRecording.recording_state = {
|
||||||
|
syncing_error_details: 'sync errors in container',
|
||||||
|
verification_error_details: 'verification error',
|
||||||
|
};
|
||||||
|
|
||||||
|
featuresService.enable('ssh-session-recording');
|
||||||
|
|
||||||
|
await visit(urls.sessionRecordings);
|
||||||
|
|
||||||
|
await click(commonSelectors.HREF(urls.sessionRecording));
|
||||||
|
|
||||||
|
assert.dom(selectors.SESSION_PLAYBACK_ERROR_ALERT).isVisible();
|
||||||
|
|
||||||
|
const errorMessages = findAll(selectors.SESSION_PLAYBACK_ERROR_MESSAGE).map(
|
||||||
|
(err) => err.textContent.trim(),
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.ok(errorMessages[0].includes('sync errors in container'));
|
||||||
|
assert.ok(errorMessages[1].includes('verification error'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('flyout opens, displays errors, and can be closed', async function (assert) {
|
||||||
|
setRunOptions({
|
||||||
|
rules: {
|
||||||
|
// [ember-a11y-ignore]: axe rule "color-contrast" automatically ignored on 2026-05-27
|
||||||
|
'color-contrast': { enabled: false },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
instances.sessionRecording.recording_state = {
|
||||||
|
syncing_error_details: 'Test sync error line 1\nTest sync error line 2',
|
||||||
|
verification_error_details: 'Test verification error',
|
||||||
|
};
|
||||||
|
featuresService.enable('ssh-session-recording');
|
||||||
|
|
||||||
|
await visit(urls.sessionRecordings);
|
||||||
|
await click(commonSelectors.HREF(urls.sessionRecording));
|
||||||
|
|
||||||
|
assert.dom(selectors.SESSION_PLAYBACK_ERROR_ALERT).isVisible();
|
||||||
|
assert
|
||||||
|
.dom(`${selectors.SESSION_PLAYBACK_ERROR_ALERT} button`)
|
||||||
|
.hasTextContaining('Show more');
|
||||||
|
|
||||||
|
// Open the flyout
|
||||||
|
await click(`${selectors.SESSION_PLAYBACK_ERROR_ALERT} button`);
|
||||||
|
assert.dom(selectors.SESSION_ERROR_FLYOUT).isVisible();
|
||||||
|
|
||||||
|
// Check for sync error section and copy button
|
||||||
|
assert.dom(selectors.SESSION_ERROR_FLYOUT_ACCORDION_ITEM).isVisible();
|
||||||
|
assert.dom(selectors.SESSION_ERROR_FLYOUT_COPY_BUTTON).isVisible();
|
||||||
|
// Close the flyout
|
||||||
|
await click(selectors.SESSION_ERROR_FLYOUT_CLOSE);
|
||||||
|
|
||||||
|
assert.dom(selectors.SESSION_ERROR_FLYOUT).isNotVisible();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
@@ -22,3 +22,18 @@ export const MANAGE_DROPDOWN =
|
|||||||
'[data-test-manage-channels-dropdown] button:first-child';
|
'[data-test-manage-channels-dropdown] button:first-child';
|
||||||
export const DELETE_ACTION =
|
export const DELETE_ACTION =
|
||||||
'[data-test-manage-channels-dropdown] ul li:last-child button';
|
'[data-test-manage-channels-dropdown] ul li:last-child button';
|
||||||
|
|
||||||
|
// Playback error selectors
|
||||||
|
export const SESSION_PLAYBACK_ERROR_ALERT =
|
||||||
|
'[data-test-session-playback-alert]';
|
||||||
|
export const SESSION_PLAYBACK_ERROR_MESSAGE = '.playback-error-message';
|
||||||
|
export const SESSION_PLAYBACK_ERROR_COPY_BUTTON =
|
||||||
|
'.playback-error-item .hds-copy-button';
|
||||||
|
|
||||||
|
// Flyout and alert selectors
|
||||||
|
export const SESSION_ERROR_FLYOUT = '[data-test-error-flyout]';
|
||||||
|
export const SESSION_ERROR_FLYOUT_COPY_BUTTON =
|
||||||
|
'[data-test-error-flyout] .hds-copy-button';
|
||||||
|
export const SESSION_ERROR_FLYOUT_ACCORDION_ITEM =
|
||||||
|
'[data-test-error-flyout] .hds-accordion-item';
|
||||||
|
export const SESSION_ERROR_FLYOUT_CLOSE = '[data-test-error-flyout] button';
|
||||||
+127
@@ -135,5 +135,132 @@ module(
|
|||||||
|
|
||||||
assert.notEqual(sessionRecording.delete_after, deleteAfter);
|
assert.notEqual(sessionRecording.delete_after, deleteAfter);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('isSessionInprogressWithNoConnections handles session with syncing_error_details', function (assert) {
|
||||||
|
const sessionRecording = store.createRecord('session-recording', {
|
||||||
|
state: STATE_SESSION_RECORDING_STARTED,
|
||||||
|
connection_recordings: [],
|
||||||
|
recording_state: {
|
||||||
|
syncing_error_details: 'sync error occurred',
|
||||||
|
verification_error_details: null,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
controller.set('model', { sessionRecording });
|
||||||
|
|
||||||
|
assert.true(controller.isSessionInprogressWithNoConnections);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('isSessionUnknownWithNoConnections handles session with verification_error_details', function (assert) {
|
||||||
|
const sessionRecording = store.createRecord('session-recording', {
|
||||||
|
state: STATE_SESSION_RECORDING_UNKNOWN,
|
||||||
|
connection_recordings: [],
|
||||||
|
recording_state: {
|
||||||
|
syncing_error_details: null,
|
||||||
|
verification_error_details: 'verification failed',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
controller.set('model', { sessionRecording });
|
||||||
|
|
||||||
|
assert.true(controller.isSessionUnknownWithNoConnections);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('isSessionUnknownWithNoConnections handles session with both error types', function (assert) {
|
||||||
|
const sessionRecording = store.createRecord('session-recording', {
|
||||||
|
state: STATE_SESSION_RECORDING_UNKNOWN,
|
||||||
|
connection_recordings: [],
|
||||||
|
recording_state: {
|
||||||
|
syncing_error_details: 'sync error occurred',
|
||||||
|
verification_error_details: 'verification failed',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
controller.set('model', { sessionRecording });
|
||||||
|
|
||||||
|
assert.true(controller.isSessionUnknownWithNoConnections);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('model handles recording_state with only syncing_error_details', function (assert) {
|
||||||
|
const sessionRecording = store.createRecord('session-recording', {
|
||||||
|
state: STATE_SESSION_RECORDING_UNKNOWN,
|
||||||
|
connection_recordings: [],
|
||||||
|
recording_state: {
|
||||||
|
syncing_error_details:
|
||||||
|
'sync errors in container "sr_jovU2XLzBP.bsr": storage error',
|
||||||
|
verification_error_details: null,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
controller.set('model', { sessionRecording });
|
||||||
|
|
||||||
|
assert.strictEqual(
|
||||||
|
sessionRecording.recording_state.syncing_error_details,
|
||||||
|
'sync errors in container "sr_jovU2XLzBP.bsr": storage error',
|
||||||
|
);
|
||||||
|
assert.strictEqual(
|
||||||
|
sessionRecording.recording_state.verification_error_details,
|
||||||
|
null,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('model handles recording_state with only verification_error_details', function (assert) {
|
||||||
|
const sessionRecording = store.createRecord('session-recording', {
|
||||||
|
state: STATE_SESSION_RECORDING_UNKNOWN,
|
||||||
|
connection_recordings: [],
|
||||||
|
recording_state: {
|
||||||
|
syncing_error_details: null,
|
||||||
|
verification_error_details:
|
||||||
|
'integrity check failed for session recording file',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
controller.set('model', { sessionRecording });
|
||||||
|
|
||||||
|
assert.strictEqual(
|
||||||
|
sessionRecording.recording_state.syncing_error_details,
|
||||||
|
null,
|
||||||
|
);
|
||||||
|
assert.strictEqual(
|
||||||
|
sessionRecording.recording_state.verification_error_details,
|
||||||
|
'integrity check failed for session recording file',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('model handles recording_state with both error details', function (assert) {
|
||||||
|
const syncError = 'sync errors in container';
|
||||||
|
const verifyError = 'integrity check failed';
|
||||||
|
const sessionRecording = store.createRecord('session-recording', {
|
||||||
|
state: STATE_SESSION_RECORDING_UNKNOWN,
|
||||||
|
connection_recordings: [],
|
||||||
|
recording_state: {
|
||||||
|
syncing_error_details: syncError,
|
||||||
|
verification_error_details: verifyError,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
controller.set('model', { sessionRecording });
|
||||||
|
|
||||||
|
assert.strictEqual(
|
||||||
|
sessionRecording.recording_state.syncing_error_details,
|
||||||
|
syncError,
|
||||||
|
);
|
||||||
|
assert.strictEqual(
|
||||||
|
sessionRecording.recording_state.verification_error_details,
|
||||||
|
verifyError,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('model handles empty recording_state object', function (assert) {
|
||||||
|
const sessionRecording = store.createRecord('session-recording', {
|
||||||
|
state: STATE_SESSION_RECORDING_UNKNOWN,
|
||||||
|
connection_recordings: [],
|
||||||
|
recording_state: {},
|
||||||
|
});
|
||||||
|
controller.set('model', { sessionRecording });
|
||||||
|
|
||||||
|
assert.strictEqual(
|
||||||
|
sessionRecording.recording_state.syncing_error_details,
|
||||||
|
undefined,
|
||||||
|
);
|
||||||
|
assert.strictEqual(
|
||||||
|
sessionRecording.recording_state.verification_error_details,
|
||||||
|
undefined,
|
||||||
|
);
|
||||||
|
});
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
Reference in new issue
Block a user