115 lines
5.5 KiB
JavaScript
115 lines
5.5 KiB
JavaScript
const test = require('node:test');
|
|
const assert = require('node:assert/strict');
|
|
const fs = require('node:fs');
|
|
|
|
require('../src/common');
|
|
|
|
const { buildUsersEditViewModel, buildUsersAddViewModel } = require('../src/web/routes/settings/users/form-view-model');
|
|
const rbacData = require('../src/web/lib/auth/rbac-data');
|
|
const usersFormTemplate = fs.readFileSync(require.resolve('../src/web/views/settings/users/form.hbs'), 'utf8');
|
|
|
|
test('user edit view model includes delete confirmation text', () => {
|
|
const model = buildUsersEditViewModel({ id: 12, inUse: false, roleIds: [] }, 'Saved.', { id: 1 }, []);
|
|
|
|
assert.equal(model.footerDeleteUrl, '/settings/users/12/delete');
|
|
assert.equal(model.footerDeleteConfirmMessage, 'Delete this user?');
|
|
assert.equal(model.footerDeleteDisabled, false);
|
|
assert.equal(model.showSaveSecondaryActions, true);
|
|
assert.equal(model.formAttrs, 'data-async-save data-async-save-close-url="/settings/users" data-async-save-new-url="/settings/users/new"');
|
|
});
|
|
|
|
test('user add view model keeps delete disabled without a confirm message', () => {
|
|
const model = buildUsersAddViewModel('Saved.', { id: 1 }, [], { name: 'New User', username: 'newuser' }, 'primary');
|
|
|
|
assert.equal(model.footerDeleteDisabled, true);
|
|
assert.equal(model.footerDeleteUrl, '');
|
|
assert.equal(model.footerDeleteConfirmMessage, undefined);
|
|
assert.equal(model.formAttrs, 'data-async-save data-async-save-close-url="/settings/users" data-async-save-new-redirect="response-url" data-async-save-new-url="/settings/users/new"');
|
|
});
|
|
|
|
test('user add view model uses success messaging for redirected saves', () => {
|
|
const model = buildUsersAddViewModel('User created.', { id: 1 }, [], { name: 'New User', username: 'newuser' });
|
|
|
|
assert.equal(model.messageVariant, 'success');
|
|
});
|
|
|
|
test('user form keeps username editable in the admin section', () => {
|
|
assert.ok(usersFormTemplate.includes('name="username"'));
|
|
assert.ok(!usersFormTemplate.includes('{{#if isEdit}}disabled{{else}}required{{/if}}'));
|
|
assert.ok(usersFormTemplate.includes('required />'));
|
|
assert.match(usersFormTemplate, /class="btn-group ms-auto" role="group" aria-label=/);
|
|
});
|
|
|
|
test('user edit view model carries Better Auth sign-in methods without provider account ids', async () => {
|
|
const pool = {
|
|
async query(sql) {
|
|
if (String(sql).includes('FROM a_users u')) {
|
|
return [[{ id: 12, name: 'User', username: 'user', email: 'user@example.test', email_verified: 1, two_factor_enabled: 0, role_ids_csv: '' }]];
|
|
}
|
|
if (String(sql).includes('FROM a_accounts')) {
|
|
return [[{ provider_id: 'credential', account_id: '12' }, { provider_id: 'google', account_id: 'google-subject' }]];
|
|
}
|
|
if (String(sql).includes('FROM a_user_passkeys')) return [[{ id: 'passkey-1', name: 'Work laptop', created_at: new Date() }]];
|
|
return [[]];
|
|
}
|
|
};
|
|
|
|
const user = await rbacData.fetchUserWithRoles(pool, 12);
|
|
const model = buildUsersEditViewModel(user, '', { id: 1 }, []);
|
|
|
|
assert.deepEqual(model.authMethods, [
|
|
{ providerId: 'credential', label: 'Password', isCredential: true },
|
|
{ providerId: 'google', label: 'Google', isCredential: false }
|
|
]);
|
|
assert.equal(model.user.twoFactorEnabled, false);
|
|
assert.deepEqual(model.passkeys, [{ id: 'passkey-1', name: 'Work laptop', created_at: model.passkeys[0].created_at }]);
|
|
assert.equal(model.user.canUnlinkPasskeys, true);
|
|
assert.doesNotMatch(JSON.stringify(model.authMethods), /google-subject/);
|
|
assert.ok(usersFormTemplate.includes('Sign-in methods'));
|
|
assert.ok(usersFormTemplate.includes('{{#if isEdit}}'));
|
|
assert.doesNotMatch(usersFormTemplate, /Linked SSO or OAuth accounts will appear here when configured\./);
|
|
assert.ok(usersFormTemplate.includes('user.passkeys'));
|
|
assert.ok(usersFormTemplate.includes('data-admin-passkey-group'));
|
|
assert.ok(usersFormTemplate.includes('data-admin-passkey-list'));
|
|
assert.ok(usersFormTemplate.includes('bi-arrow-return-right'));
|
|
assert.ok(usersFormTemplate.includes('/passkeys/{{id}}/unlink'));
|
|
assert.ok(usersFormTemplate.includes('list-group-item d-flex align-items-start gap-3 px-0'));
|
|
});
|
|
|
|
test('password-only users still see the available two-factor option', () => {
|
|
const model = buildUsersEditViewModel({
|
|
id: 12,
|
|
inUse: false,
|
|
roleIds: [],
|
|
twoFactorEnabled: false,
|
|
authMethods: [{ providerId: 'credential', label: 'Password' }]
|
|
}, '', { id: 1 }, []);
|
|
|
|
assert.equal(model.user.twoFactorEnabled, false);
|
|
assert.ok(usersFormTemplate.includes('Two-step verification'));
|
|
assert.ok(usersFormTemplate.includes('Not enabled'));
|
|
});
|
|
|
|
test('admin edit shows two-factor status as an additional sign-in method', async () => {
|
|
const pool = {
|
|
async query(sql) {
|
|
if (String(sql).includes('FROM a_users u')) {
|
|
return [[{ id: 13, name: 'User', username: 'user', email: 'user@example.test', email_verified: 1, two_factor_enabled: 1, role_ids_csv: '' }]];
|
|
}
|
|
if (String(sql).includes('FROM a_accounts')) return [[{ provider_id: 'credential', account_id: '13' }]];
|
|
return [[]];
|
|
}
|
|
};
|
|
|
|
const user = await rbacData.fetchUserWithRoles(pool, 13);
|
|
const model = buildUsersEditViewModel(user, '', { id: 1 }, []);
|
|
assert.equal(model.user.twoFactorEnabled, true);
|
|
assert.ok(usersFormTemplate.includes('Enabled'));
|
|
});
|
|
|
|
test('user edit view model withholds passkey unlink from the account owner', () => {
|
|
const user = { id: 12, passkeys: [{ id: 'passkey-1', name: 'Work laptop' }] };
|
|
const model = buildUsersEditViewModel(user, '', { id: 12 }, []);
|
|
assert.equal(model.user.canUnlinkPasskeys, false);
|
|
assert.equal(model.passkeys.length, 1);
|
|
}); |