Files
upstream-zulip/web/tests/people.test.cjs
upstream-mirror 64898b2c79
Some checks failed
Code scanning / CodeQL (push) Failing after 15s
Zulip production suite / Ubuntu 22.04 production build (push) Failing after 26s
Zulip production suite / Debian 12 production install with custom db name and user (push) Has been skipped
Zulip production suite / Ubuntu 22.04 production install and PostgreSQL upgrade with pgroonga (push) Has been skipped
Zulip production suite / Ubuntu 24.04 production install (push) Has been skipped
Zulip production suite / Ubuntu 26.04 production install (push) Has been skipped
Zulip production suite / Debian 13 production install (push) Has been skipped
Zulip production suite / 7.0 Version Upgrade (push) Has been skipped
Zulip production suite / 8.0 Version Upgrade (push) Has been skipped
Zulip production suite / 6.0 Version Upgrade (push) Has been skipped
Zulip production suite / 10.0 Version Upgrade (push) Has been skipped
Zulip production suite / 9.0 Version Upgrade (push) Has been skipped
Zulip production suite / 12.0 Version Upgrade (push) Has been skipped
Zulip production suite / 11.0 Version Upgrade (push) Has been skipped
GitHub Actions Security Analysis with zizmor / zizmor (push) Failing after 3s
Zulip CI / Debian 12 (Python 3.11, backend + documentation) (push) Failing after 26s
Zulip CI / Ubuntu 22.04 (Python 3.10, backend + frontend) (push) Failing after 3s
Zulip CI / Ubuntu 24.04 (Python 3.12, backend) (push) Failing after 25s
Zulip CI / Ubuntu 26.04 (Python 3.14, backend) (push) Failing after 25s
Zulip CI / Debian 13 (Python 3.13, backend) (push) Failing after 28s
Zulip production suite / Required jobs (push) Has been cancelled
Zulip CI / Required jobs (push) Has been cancelled
API Documentation Update Check / check-feature-level-updated (push) Has been cancelled
API Documentation Update Check / notify-if-api-docs-changed (push) Has been cancelled
snapshot of https://github.com/zulip/zulip.git @ 48c69911c7dd4e03b20552eb3bd77ad8151c1357 (2026-09-15)
2026-09-15 15:05:30 +05:00

2263 lines
74 KiB
JavaScript

"use strict";
const assert = require("node:assert/strict");
const {parseISO} = require("date-fns");
const _ = require("lodash");
const {make_user_group} = require("./lib/example_group.cjs");
const {make_realm} = require("./lib/example_realm.cjs");
const {make_bot, make_cross_realm_bot, make_user, Role} = require("./lib/example_user.cjs");
const {$t} = require("./lib/i18n.cjs");
const {clock, mock_esm, zrequire, set_global} = require("./lib/namespace.cjs");
const {run_test, noop} = require("./lib/test.cjs");
const blueslip = require("./lib/zblueslip.cjs");
const {page_params} = require("./lib/zpage_params.cjs");
const message_user_ids = mock_esm("../src/message_user_ids");
const settings_data = mock_esm("../src/settings_data", {
user_can_access_all_other_users: () => true,
});
mock_esm("../src/server_events_state", {
has_received_first_events_response: () => true,
});
const channel = mock_esm("../src/channel");
let additional_calls_before_set_timeout = noop;
set_global("setTimeout", (func) => {
additional_calls_before_set_timeout();
func();
});
const muted_users = zrequire("muted_users");
const people = zrequire("people");
const retry_backoff = zrequire("retry_backoff");
const settings_config = zrequire("../src/settings_config.ts");
const {set_current_user, set_realm} = zrequire("state_data");
const user_groups = zrequire("user_groups");
const {initialize_user_settings} = zrequire("user_settings");
const current_user = {};
set_current_user(current_user);
const realm = make_realm();
set_realm(realm);
const user_settings = {};
initialize_user_settings({user_settings});
const welcome_bot = make_cross_realm_bot({
email: "welcome-bot@example.com",
user_id: 4,
full_name: "Welcome Bot",
// cross realm bots have no owner
});
const email_gateway_bot = make_cross_realm_bot({
email: "emailgateway@example.com",
user_id: 5,
full_name: "Email Gateway",
// cross realm bots have no owner
});
const me = make_user({
email: "me@example.com",
user_id: 30,
full_name: "Me Myself",
timezone: "America/Los_Angeles",
// no avatar, so client should construct a /avatar/{user_id} URL.
});
const isaac = make_user({
email: "isaac@example.com",
delivery_email: "isaac-delivery@example.com",
user_id: 32,
full_name: "Isaac Newton",
});
const unknown_user = people.make_user(1500, "unknown@example.com", "Unknown user");
function initialize() {
additional_calls_before_set_timeout = noop;
people.init();
people.add_active_user({...me});
people.initialize_current_user(me.user_id);
muted_users.set_muted_users([]);
people._add_user(unknown_user);
}
const nobody = make_user_group({
name: "role:nobody",
id: 1,
members: new Set(),
is_system_group: true,
direct_subgroup_ids: new Set(),
});
const everyone = make_user_group({
name: "role:everyone",
id: 2,
members: new Set([30]),
is_system_group: true,
direct_subgroup_ids: new Set(),
});
user_groups.initialize({realm_user_groups: [nobody, everyone]});
/*
TEST SETUP NOTES:
We don't add all these users right away,
but they are convenient to have for various
tests, and we just add them as needed after
calling something like `people.init()`.
Note that we deliberately make it so that
alphabetical order mismatches id order,
since that can uncover bugs where we neglect
to be rigorous about sort order.
*/
const realm_admin = make_user({
email: "realm_admin@example.com",
full_name: "Realm Admin",
user_id: 32,
role: Role.ADMINISTRATOR,
});
const guest = make_user({
email: "guest@example.com",
full_name: "Guest User",
user_id: 33,
role: Role.GUEST,
});
const realm_owner = make_user({
email: "realm_owner@example.com",
full_name: "Realm Owner",
user_id: 34,
role: Role.OWNER,
});
const bot_botson = make_bot({
email: "botson-bot@example.com",
user_id: 35,
full_name: "Bot Botson",
bot_owner_id: isaac.user_id,
role: Role.MODERATOR,
});
const moderator = make_user({
email: "moderator@example.com",
full_name: "Moderator",
user_id: 36,
role: Role.MODERATOR,
});
const bot_with_inaccessible_owner = make_bot({
email: "inaccessible-owner-bot@example.com",
user_id: 37,
full_name: "Inaccessible owner bot",
bot_owner_id: 38,
role: Role.MODERATOR,
});
const steven = make_user({
email: "steven@example.com",
delivery_email: "steven-delivery@example.com",
user_id: 77,
full_name: "Steven",
});
const alice1 = make_user({
email: "alice1@example.com",
delivery_email: "alice1-delivery@example.com",
user_id: 202,
full_name: "Alice",
});
const bob = make_user({
email: "bob@example.com",
delivery_email: "bob-delivery@example.com",
user_id: 203,
full_name: "Bob van Roberts",
});
const charles = make_user({
email: "charles@example.com",
user_id: 301,
full_name: "Charles Dickens",
avatar_url: "http://charles.com/foo.png",
});
const maria = make_user({
email: "Athens@example.com",
user_id: 302,
full_name: "Maria Athens",
// With client_gravatar enabled, requests that client compute gravatar
avatar_url: null,
});
const cedar = make_user({
email: "Cedar@example.com",
user_id: 305,
full_name: "Cedar Athens",
// With client_gravatar enabled, requests that client compute gravatar
avatar_url: null,
});
const leo = make_user({
email: "Leo@example.com",
user_id: 306,
full_name: "Leo Athens",
// With client_gravatar enabled, requests that client compute gravatar
avatar_url: null,
});
const ashton = make_user({
email: "ashton@example.com",
user_id: 303,
full_name: "Ashton Smith",
});
const linus = make_user({
email: "ltorvalds@example.com",
user_id: 304,
full_name: "Linus Torvalds",
});
// A user whose full name starts with digits, used to verify that
// id matching is exact and does not collide with digit-leading names.
const digit_named = make_user({
email: "digit-named@example.com",
user_id: 9001,
full_name: "304 Robot",
});
const emp401 = make_user({
email: "emp401@example.com",
user_id: 401,
full_name: "whatever 401",
});
const emp402 = make_user({
email: "EMP402@example.com",
user_id: 402,
full_name: "whatever 402",
});
const debbie = make_user({
email: "deBBie71@example.com",
user_id: 501,
full_name: "Debra Henton",
});
const stephen1 = make_user({
email: "stephen-the-author@example.com",
user_id: 601,
full_name: "Stephen King",
});
const stephen2 = make_user({
email: "stephen-the-explorer@example.com",
user_id: 602,
full_name: "Stephen King",
});
const noah = make_user({
email: "emnoa@example.com",
user_id: 1200,
full_name: "Nöôáàh Ëmerson",
});
const plain_noah = make_user({
email: "otheremnoa@example.com",
user_id: 1201,
full_name: "Nooaah Emerson",
});
const all1 = make_user({
email: "all1@example.com",
user_id: 1202,
full_name: "all",
});
const all2 = make_user({
email: "all2@example.com",
user_id: 1203,
full_name: "all",
});
const stewie = make_user({
email: "stewie@example.com",
user_id: 1204,
full_name: "Stewart Gilligan",
profile_data: {
1: "(888) 888-8888",
2: "(555) 555-5555",
3: "he/him",
},
});
const imported_user = {
email: "imported-user@example.com",
user_id: 1205,
full_name: "Imported user",
is_imported_stub: true,
};
function get_all_persons() {
return people.filter_all_persons(() => true);
}
run_test("basics", ({override}) => {
initialize();
const persons = get_all_persons();
assert.deepEqual(people.get_realm_users(), [me]);
assert.equal(persons.length, 2);
assert.equal(persons[0].full_name, "Me Myself");
let realm_persons = people.get_realm_users();
assert.equal(realm_persons[0].full_name, "Me Myself");
realm_persons = people.get_realm_users();
assert.equal(realm_persons.length, 1);
assert.equal(people.get_realm_active_human_user_ids().length, 1);
const full_name = "Isaac Newton";
const email = "isaac@example.com";
assert.ok(!people.is_known_user_id(32));
assert.ok(!people.is_valid_full_name_and_user_id(full_name, 32));
assert.equal(people.get_user_id_from_name(full_name), undefined);
people.add_active_user(isaac);
assert.equal(people.get_actual_name_from_user_id(32), full_name);
assert.ok(people.is_valid_full_name_and_user_id(full_name, 32));
assert.ok(people.is_known_user_id(32));
assert.equal(people.get_active_human_count(), 2);
assert.equal(people.get_user_id_from_name(full_name), 32);
let person = people.get_by_email(email);
assert.equal(person.full_name, full_name);
realm_persons = people.get_realm_users();
assert.equal(realm_persons.length, 2);
const active_user_ids = people.get_active_user_ids().toSorted();
assert.deepEqual(active_user_ids, [me.user_id, isaac.user_id]);
assert.equal(people.is_active_user_or_system_bot(isaac.user_id), true);
assert.ok(people.is_valid_user_id_for_compose(isaac.user_id));
let bot_user_ids = people.get_bot_ids();
assert.equal(bot_user_ids.length, 0);
// Now deactivate isaac
people.deactivate(isaac);
assert.equal(people.get_non_active_human_ids().length, 1);
assert.equal(people.get_non_active_user_ids_count([isaac.user_id]), 1);
assert.equal(people.get_active_human_count(), 1);
assert.equal(people.is_active_user_or_system_bot(isaac.user_id), false);
assert.equal(people.is_valid_user_id_for_compose(isaac.user_id), true);
people.add_active_user(bot_botson);
assert.equal(people.is_active_user_or_system_bot(bot_botson.user_id), true);
bot_user_ids = people.get_bot_ids();
assert.deepEqual(bot_user_ids, [bot_botson.user_id]);
assert.equal(people.get_bot_owner_user(bot_botson).full_name, "Isaac Newton");
assert.equal(people.can_admin_user(me), true);
assert.equal(people.can_admin_user(bot_botson), false);
// Add our cross-realm bot. It won't add to our human
// count, and it has no owner.
people.add_cross_realm_user(welcome_bot);
people.add_cross_realm_user(email_gateway_bot);
assert.equal(people.get_bot_owner_user(welcome_bot), undefined);
assert.equal(people.get_bot_owner_user(email_gateway_bot), undefined);
assert.equal(people.get_active_human_count(), 1);
assert.equal(people.get_by_email(welcome_bot.email).full_name, "Welcome Bot");
assert.equal(people.get_by_email(email_gateway_bot.email).full_name, "Email Gateway");
override(settings_data, "user_can_access_all_other_users", () => false);
assert.equal(
people.get_bot_owner_user(bot_with_inaccessible_owner).full_name,
"translated: Unknown user",
);
// get_realm_users() will include our active bot,
// but will exclude isaac (who is deactivated)
assert.deepEqual(
people
.get_realm_users()
.map((u) => u.user_id)
.toSorted(),
[me.user_id, bot_botson.user_id],
);
// get_bot_ids() includes all bot users.
bot_user_ids = people.get_bot_ids();
assert.deepEqual(bot_user_ids, [
bot_botson.user_id,
welcome_bot.user_id,
email_gateway_bot.user_id,
]);
// The bot doesn't add to our human count.
assert.equal(people.get_active_human_count(), 1);
// Invalid user ID returns true and warns.
blueslip.expect("warn", "Unexpectedly invalid user_id in user popover query");
assert.equal(people.is_active_user_or_system_bot(123412), true);
unknown_user.is_inaccessible_user = true;
assert.equal(people.is_active_user_or_system_bot(unknown_user.user_id), true);
unknown_user.is_inaccessible_user = false;
// We can still get their info for non-realm needs.
person = people.get_by_email(email);
assert.equal(person.email, email);
// The current user should still be there
person = people.get_by_email("me@example.com");
assert.equal(person.full_name, "Me Myself");
// Test is_my_user_id function
assert.equal(people.is_my_user_id(me.user_id), true);
assert.equal(people.is_my_user_id(isaac.user_id), false);
// Reactivating issac
people.add_active_user(isaac);
const active_humans = people.get_realm_active_human_user_ids();
assert.equal(active_humans.length, 2);
assert.deepEqual(active_humans.toSorted(), [me.user_id, isaac.user_id]);
// get_users_from_ids
assert.deepEqual(people.get_users_from_ids([me.user_id, isaac.user_id]), [me, isaac]);
// Test with imported user.
people.add_active_user(imported_user);
assert.equal(people.get_realm_active_human_user_ids().length, 3);
assert.equal(people.get_realm_active_human_user_ids_for_users_panel().length, 2);
assert.equal(people.get_realm_active_imported_stub_user_ids().length, 1);
// Check get_realm_active_imported_stub_user_ids does not include deactivated users.
assert.equal(people.get_non_active_human_ids().length, 0);
people.deactivate(imported_user);
assert.equal(people.get_realm_active_human_user_ids().length, 2);
assert.equal(people.get_realm_active_human_user_ids_for_users_panel().length, 2);
assert.equal(people.get_realm_active_imported_stub_user_ids().length, 0);
assert.equal(people.get_non_active_human_ids().length, 1);
});
run_test("sort_but_pin_current_user_on_top with me", () => {
initialize();
people.add_active_user(maria);
people.add_active_user(steven);
// We need the actual object from people.js, not the
// "me" object we made a copy of.
const my_user = people.get_by_user_id(me.user_id);
const users = [steven, debbie, maria, my_user];
people.sort_but_pin_current_user_on_top(users);
assert.deepEqual(users, [my_user, debbie, maria, steven]);
});
run_test("sort_but_pin_current_user_on_top without me", () => {
initialize();
people.add_active_user(maria);
people.add_active_user(steven);
const users = [steven, maria];
people.sort_but_pin_current_user_on_top(users);
assert.deepEqual(users, [maria, steven]);
});
run_test("check_active_non_active_users", ({override}) => {
initialize();
people.add_active_user(bot_botson);
people.add_active_user(isaac);
let active_users = people.get_realm_users();
let non_active_users = people.get_non_active_realm_users();
assert.equal(active_users.length, 3);
assert.equal(non_active_users.length, 0);
people.add_active_user(maria);
people.add_active_user(linus);
active_users = people.get_realm_users();
assert.equal(active_users.length, 5);
// Invalid ID
blueslip.expect("error", "No user found");
people.is_person_active(1000001);
assert.equal(people.is_person_active(maria.user_id), true);
assert.equal(people.is_person_active(linus.user_id), true);
people.deactivate(maria);
non_active_users = people.get_non_active_realm_users();
active_users = people.get_realm_users();
assert.equal(non_active_users.length, 1);
assert.equal(active_users.length, 4);
assert.equal(people.is_person_active(maria.user_id), false);
people.deactivate(linus);
people.add_active_user(maria);
non_active_users = people.get_non_active_realm_users();
active_users = people.get_realm_users();
assert.equal(non_active_users.length, 1);
assert.equal(active_users.length, 4);
assert.equal(people.is_person_active(maria.user_id), true);
assert.equal(people.is_person_active(linus.user_id), false);
// If user cannot access a user, that user will be treated as active.
override(settings_data, "user_can_access_all_other_users", () => false);
assert.equal(people.is_person_active(99), true);
});
run_test("pm_lookup_key", () => {
initialize();
assert.equal(people.pm_lookup_key("30"), "30");
assert.equal(people.pm_lookup_key("32,30"), "32");
assert.equal(people.pm_lookup_key("101,32,30"), "32,101");
});
run_test("get_recipients", () => {
initialize();
people.add_active_user(isaac);
people.add_active_user(linus);
assert.deepEqual(people.get_recipients("30"), ["Me Myself"]);
assert.deepEqual(people.get_recipients("30,32"), ["Isaac Newton"]);
muted_users.add_muted_user(304);
assert.deepEqual(people.get_recipients("304,32"), ["Isaac Newton", "translated: Muted user"]);
});
run_test("get_full_name", () => {
initialize();
people.add_active_user(isaac);
const names = people.get_full_name(isaac.user_id);
assert.equal(names, "Isaac Newton");
});
run_test("get_full_names_for_poll_option", () => {
initialize();
people.add_active_user(isaac);
const names = people.get_full_names_for_poll_option([me.user_id, isaac.user_id]);
assert.equal(names, "Me Myself, Isaac Newton");
});
run_test("get_display_full_names", ({override}) => {
initialize();
people.initialize_current_user(me.user_id);
people.add_active_user(steven);
people.add_active_user(bob);
people.add_active_user(charles);
people.add_active_user(guest);
override(realm, "realm_enable_guest_user_indicator", true);
let user_ids = [me.user_id, steven.user_id, bob.user_id, charles.user_id, guest.user_id];
let names = people.get_display_full_names(user_ids);
// This doesn't do anything special for the current user. The caller has
// to take care of such cases and do the appropriate.
assert.deepEqual(names, [
"Me Myself",
"Steven",
"Bob van Roberts",
"Charles Dickens",
"translated: Guest User (guest)",
]);
muted_users.add_muted_user(charles.user_id);
override(realm, "realm_enable_guest_user_indicator", false);
names = people.get_display_full_names(user_ids);
assert.deepEqual(names, [
"Me Myself",
"Steven",
"Bob van Roberts",
"translated: Muted user",
"Guest User",
]);
muted_users.add_muted_user(guest.user_id);
names = people.get_display_full_names(user_ids);
assert.deepEqual(names, [
"Me Myself",
"Steven",
"Bob van Roberts",
"translated: Muted user",
"translated: Muted user",
]);
override(realm, "realm_enable_guest_user_indicator", true);
names = people.get_display_full_names(user_ids);
assert.deepEqual(names, [
"Me Myself",
"Steven",
"Bob van Roberts",
"translated: Muted user",
"translated: Muted user (guest)",
]);
override(settings_data, "user_can_access_all_other_users", () => false);
const inaccessible_user_id = 99;
user_ids = [me.user_id, steven.user_id, inaccessible_user_id];
names = people.get_display_full_names(user_ids, true);
assert.deepEqual(names, ["Me Myself", "Steven", "translated: Unknown user"]);
});
run_test("my_custom_profile_data", () => {
initialize();
const person = people.get_by_email(me.email);
person.profile_data = {3: "My address", 4: "My phone number"};
assert.equal(people.my_custom_profile_data(3), "My address");
assert.equal(people.my_custom_profile_data(4), "My phone number");
});
run_test("get_custom_fields_by_type", ({override}) => {
initialize();
people.add_active_user(stewie);
const person = people.get_by_user_id(stewie.user_id);
override(realm, "custom_profile_field_types", {
SHORT_TEXT: {
id: 1,
name: "Short text",
},
PRONOUNS: {
id: 8,
name: "Pronouns",
},
});
override(realm, "custom_profile_fields", [
{
id: 1,
name: "Phone number (mobile)",
type: 1,
},
{
id: 2,
name: "Phone number (office)",
type: 1,
},
{
id: 3,
name: "Pronouns",
type: 8,
},
]);
const SHORT_TEXT_ID = 1;
assert.deepEqual(people.get_custom_fields_by_type(person.user_id, SHORT_TEXT_ID), [
"(888) 888-8888",
"(555) 555-5555",
]);
assert.deepEqual(people.get_custom_fields_by_type(person.user_id, 8), ["he/him"]);
assert.deepEqual(people.get_custom_fields_by_type(person.user_id, 100), []);
// Bots have no profile_data, so the function returns undefined.
people.add_active_user(bot_botson);
assert.equal(people.get_custom_fields_by_type(bot_botson.user_id, SHORT_TEXT_ID), undefined);
});
run_test("bot_custom_profile_data", () => {
initialize();
// If this test fails, then try opening organization settings > bots
// http://localhost:9991/#organization/bots/all-bots
// and then try to edit any of the bots.
people.add_active_user(bot_botson);
assert.equal(people.get_custom_profile_data(bot_botson.user_id, 3), undefined);
});
run_test("user_timezone", ({override}) => {
initialize();
clock.setSystemTime(parseISO("20130208T080910").getTime());
override(user_settings, "twenty_four_hour_time", true);
assert.equal(people.get_user_time(me.user_id), "00:09");
override(user_settings, "twenty_four_hour_time", false);
assert.equal(people.get_user_time(me.user_id), "12:09 AM");
});
run_test("utcToZonedTime", ({override}) => {
initialize();
clock.setSystemTime(parseISO("20130208T080910").getTime());
override(user_settings, "twenty_four_hour_time", true);
assert.deepEqual(people.get_user_time(unknown_user.user_id), undefined);
assert.equal(people.get_user_time(me.user_id), "00:09");
override(people.get_by_user_id(me.user_id), "timezone", "Eriador/Rivendell");
blueslip.expect(
"warn",
"Error formatting time in Eriador/Rivendell: RangeError: Invalid time zone specified: Eriador/Rivendell",
);
people.get_user_time(me.user_id);
});
run_test("user_type", () => {
initialize();
people.init();
people.add_active_user(me);
people.add_active_user(realm_admin);
people.add_active_user(guest);
people.add_active_user(realm_owner);
people.add_active_user(moderator);
people.add_active_user(bot_botson);
assert.equal(people.get_user_type(me.user_id), $t({defaultMessage: "Member"}));
assert.equal(people.get_user_type(realm_admin.user_id), $t({defaultMessage: "Administrator"}));
assert.equal(people.get_user_type(guest.user_id), $t({defaultMessage: "Guest"}));
assert.equal(people.get_user_type(realm_owner.user_id), $t({defaultMessage: "Owner"}));
assert.equal(people.get_user_type(moderator.user_id), $t({defaultMessage: "Moderator"}));
assert.equal(people.get_user_type(bot_botson.user_id), $t({defaultMessage: "Moderator"}));
});
run_test("updates", () => {
initialize();
const person = people.get_by_email("me@example.com");
people.set_full_name(person, "Me the Third");
assert.equal(people.my_full_name(), "Me the Third");
assert.equal(person.full_name, "Me the Third");
assert.equal(people.get_user_id_from_name("Me the Third"), me.user_id);
});
run_test("get_by_user_id", () => {
initialize();
let person = make_user({
email: "mary@example.com",
user_id: 42,
full_name: "Mary",
});
people.add_active_user(person);
person = people.get_by_email("mary@example.com");
assert.equal(person.full_name, "Mary");
person = people.get_by_user_id(42);
assert.equal(person.email, "mary@example.com");
people.set_full_name(person, "Mary New");
person = people.get_by_user_id(42);
assert.equal(person.full_name, "Mary New");
// deactivate() should eventually just take a user_id, but
// now it takes a full person object. Note that deactivate()
// won't actually make the user disappear completely.
people.deactivate(person);
person = people.get_by_user_id(42);
assert.equal(person.user_id, 42);
});
run_test("set_custom_profile_field_data", () => {
initialize();
const person = people.get_by_email(me.email);
person.profile_data = {};
const field = {
id: 3,
value: "Field value",
rendered_value: "<p>Field value</p>",
};
people.set_custom_profile_field_data(person.user_id, field);
assert.equal(person.profile_data[field.id].value, "Field value");
assert.equal(person.profile_data[field.id].rendered_value, "<p>Field value</p>");
people.set_custom_profile_field_data(person.user_id, {id: 3, value: null});
assert.ok(!Object.hasOwn(person.profile_data, field.id));
});
run_test("is_current_user_only_owner", ({override}) => {
initialize();
const person = people.get_by_email(me.email);
person.is_owner = false;
override(current_user, "is_owner", false);
assert.ok(!people.is_current_user_only_owner());
person.is_owner = true;
override(current_user, "is_owner", true);
assert.ok(people.is_current_user_only_owner());
people.add_active_user(realm_owner);
assert.ok(!people.is_current_user_only_owner());
});
run_test("user_can_change_their_own_role", ({override}) => {
initialize();
const bob = make_user({
email: "bob@example.com",
user_id: 1,
full_name: "Bob",
});
people.add_active_user(bob);
const person = people.get_by_email(bob.email);
assert.ok(person);
person.is_owner = false;
override(current_user, "is_owner", true);
assert.ok(!people.user_can_change_their_own_role());
override(current_user, "is_owner", false);
override(current_user, "is_admin", true);
assert.ok(people.user_can_change_their_own_role());
override(current_user, "is_owner", false);
override(current_user, "is_admin", false);
assert.ok(!people.user_can_change_their_own_role());
person.is_owner = true;
override(current_user, "is_owner", true);
override(current_user, "is_admin", true);
assert.ok(people.user_can_change_their_own_role());
});
run_test("filtered_users", () => {
initialize();
people.add_active_user(charles);
people.add_active_user(maria);
people.add_active_user(ashton);
people.add_active_user(linus);
people.add_active_user(noah);
people.add_active_user(plain_noah);
const search_term = "a";
const users = people.get_realm_users();
let filtered_people = people.filter_people_by_search_terms(users, search_term);
assert.equal(filtered_people.size, 2);
assert.ok(filtered_people.has(ashton.user_id));
assert.ok(filtered_people.has(maria.user_id));
assert.ok(!filtered_people.has(charles.user_id));
filtered_people = people.filter_people_by_search_terms(users, "");
assert.equal(filtered_people.size, 7);
filtered_people = people.filter_people_by_search_terms(users, "ltorv");
assert.equal(filtered_people.size, 1);
assert.ok(filtered_people.has(linus.user_id));
filtered_people = people.filter_people_by_search_terms(users, "ch di, maria");
assert.equal(filtered_people.size, 2);
assert.ok(filtered_people.has(charles.user_id));
assert.ok(filtered_people.has(maria.user_id));
// Test filtering of names with diacritics
// This should match Nöôáàh by ignoring diacritics, and also match Nooaah
filtered_people = people.filter_people_by_search_terms(users, "noOa");
assert.equal(filtered_people.size, 2);
assert.ok(filtered_people.has(noah.user_id));
assert.ok(filtered_people.has(plain_noah.user_id));
// This should match ëmerson, but not emerson
filtered_people = people.filter_people_by_search_terms(users, "ëm");
assert.equal(filtered_people.size, 1);
assert.ok(filtered_people.has(noah.user_id));
// Multi-word query should ignore diacritics when the query
// itself has no diacritics.
filtered_people = people.filter_people_by_search_terms(users, "nooa em");
assert.equal(filtered_people.size, 2);
assert.ok(filtered_people.has(noah.user_id));
assert.ok(filtered_people.has(plain_noah.user_id));
// Multi-word query with diacritics should match only Nöôáàh
filtered_people = people.filter_people_by_search_terms(users, "nöôáàh Ëme");
assert.equal(filtered_people.size, 1);
assert.ok(filtered_people.has(noah.user_id));
});
run_test("dm_matches_search_string", () => {
initialize();
people.add_active_user(charles);
people.add_active_user(maria);
people.add_active_user(ashton);
people.add_active_user(linus);
people.add_active_user(noah);
people.add_active_user(plain_noah);
people.add_active_user(digit_named);
let result = people.dm_matches_search_string([ashton], "a");
assert.ok(result);
// Maria's email starts with `a`.
result = people.dm_matches_search_string([maria], "a");
assert.ok(result);
// Neither Charles' full name or email start with `a`.
result = people.dm_matches_search_string([charles], "a");
assert.ok(!result);
// Empty search terms should always return true
result = people.dm_matches_search_string([charles], "");
assert.ok(result);
result = people.dm_matches_search_string([charles, maria, noah], "");
assert.ok(result);
// Match with email.
result = people.dm_matches_search_string([linus], "ltorv");
assert.ok(result);
// Match with user id.
result = people.dm_matches_search_string([linus], "304");
assert.ok(result);
result = people.dm_matches_search_string([linus], "30");
assert.ok(!result);
// A full name starting with digits is still matched by name, and
// is not affected by the (unrelated) id of any other user.
result = people.dm_matches_search_string([digit_named], "304");
assert.ok(result);
// Test filtering of names with diacritics. This should match
// Nöôáàh by ignoring diacritics, and also match Nooaah.
result = people.dm_matches_search_string([noah], "noOa");
assert.ok(result);
result = people.dm_matches_search_string([plain_noah], "noOa");
assert.ok(result);
// This should match ëmerson, but not emerson.
result = people.dm_matches_search_string([noah], "ëm");
assert.ok(result);
result = people.dm_matches_search_string([plain_noah], "ëm");
assert.ok(!result);
});
run_test("filter_other_guest_ids", ({override}) => {
initialize();
people.add_active_user(emp401);
people.add_active_user(emp402);
people.add_active_user(guest);
assert.equal(emp401.user_id, 401);
assert.equal(emp402.user_id, 402);
assert.equal(guest.user_id, 33);
let ids = [401, 402];
let guest_ids = people.filter_other_guest_ids(ids);
assert.equal(guest_ids.length, 0);
ids = [401, 402, 33];
guest_ids = people.filter_other_guest_ids(ids);
assert.equal(guest_ids.length, 1);
assert.equal(guest_ids[0], 33);
override(current_user, "is_guest", true);
override(current_user, "user_id", 1);
// User ID of the current user will not be included in result.
guest_ids = people.filter_other_guest_ids([1]);
assert.equal(guest_ids.length, 0);
});
run_test("user_ids_to_full_names_array", () => {
initialize();
people.add_active_user(emp401);
people.add_active_user(emp402);
assert.equal(emp401.user_id, 401);
assert.equal(emp402.user_id, 402);
const ids = [401, 402];
const names = people.user_ids_to_full_names_array(ids);
assert.equal(names.length, 2);
assert.equal(names[0], emp401.full_name);
assert.equal(names[1], emp402.full_name);
});
run_test("multi_user_methods", () => {
initialize();
// Add users to valid_user_ids.
const source = "server_events";
people.add_active_user(emp401, source);
people.add_active_user(emp402, source);
// The order of user_ids is relevant here.
assert.equal(emp401.user_id, 401);
assert.equal(emp402.user_id, 402);
let user_ids = people.slug_to_user_ids("402,401");
assert.deepEqual(user_ids, [402, 401]);
user_ids = people.slug_to_user_ids("402,401-group");
assert.deepEqual(user_ids, [402, 401]);
assert.equal(people.user_ids_string_to_slug("401,402"), "401,402-group");
assert.equal(people.user_ids_string_to_slug("402"), "402-whatever-402");
assert.equal(people.user_ids_to_slug([401, 402]), "401,402-group");
assert.equal(people.user_ids_to_slug([402]), "402-whatever-402");
assert.equal(people.user_ids_to_slug([]), undefined);
});
run_test("user_ids_to_full_names_string", () => {
initialize();
people.add_active_user(charles);
people.add_active_user(maria);
assert.equal(
people.user_ids_to_full_names_string([charles.user_id, maria.user_id]),
`${charles.full_name}, ${maria.full_name}`,
);
blueslip.expect("error", "Unknown user_id in maybe_get_user_by_id");
assert.equal(
people.user_ids_to_full_names_string([charles.user_id, 9999, maria.user_id]),
`${charles.full_name}, ${maria.full_name}, translated: Unknown user`,
);
});
run_test("concat_direct_message_group", () => {
initialize();
/*
We assume that user_ids passed in
to concat_direct_message_group have
already been validated, so we don't
need actual people for these tests
to pass.
That may change in the future.
*/
const user_ids = [303, 301, 302];
assert.equal(people.concat_direct_message_group(user_ids, 304), "301,302,303,304");
// IMPORTANT: we always want to sort
// ids numerically to create direct message group strings.
assert.equal(people.concat_direct_message_group(user_ids, 99), "99,301,302,303");
});
run_test("message_methods", () => {
initialize();
people.add_active_user(charles);
people.add_active_user(maria);
people.add_active_user(cedar);
people.add_active_user(leo);
people.add_active_user(ashton);
assert.equal(people.get_muted_user_avatar_url(), "/static/images/muted-user/muted-sender.png");
assert.equal(maria.is_guest, false);
assert.equal(
people.small_avatar_url_for_person(maria),
"https://secure.gravatar.com/avatar/6dbdd7946b58d8b11351fcb27e5cdd55?d=identicon",
);
assert.equal(
maria.avatar_url,
"https://secure.gravatar.com/avatar/6dbdd7946b58d8b11351fcb27e5cdd55?d=identicon",
);
// small_avatar_url_for_user_id delegates to small_avatar_url_for_person.
assert.equal(people.small_avatar_url_for_user_id(maria.user_id), maria.avatar_url);
// This will use the cached gravatar url
assert.equal(
people.medium_avatar_url_for_person(maria),
"https://secure.gravatar.com/avatar/6dbdd7946b58d8b11351fcb27e5cdd55?d=identicon&s=500",
);
// This will create a new gravatar url
assert.equal(
people.medium_avatar_url_for_person(cedar),
"https://secure.gravatar.com/avatar/2e6ed9fc1de54b7b5bc98ced46fe7a14?d=identicon&s=500",
);
assert.equal(
cedar.avatar_url,
"https://secure.gravatar.com/avatar/2e6ed9fc1de54b7b5bc98ced46fe7a14?d=identicon",
);
assert.equal(people.medium_avatar_url_for_person(charles), "/avatar/301/medium?version=0");
assert.equal(people.medium_avatar_url_for_person(ashton), "/avatar/303/medium?version=0");
muted_users.add_muted_user(30);
assert.deepEqual(people.sender_info_for_recent_view_row([30]), [
{
...me,
avatar_url_small: "/avatar/30",
is_muted: true,
},
]);
let message = {
type: "private",
display_recipient: [{id: maria.user_id}, {id: me.user_id}, {id: charles.user_id}],
sender_id: charles.user_id,
};
assert.equal(people.pm_with_url(message), "#narrow/dm/301,302-group");
assert.equal(people.pm_perma_link(message), "#narrow/dm/30,301,302-group");
assert.equal(people.small_avatar_url(message), "http://charles.com/foo.png");
message = {
type: "private",
display_recipient: [{id: maria.user_id}, {id: me.user_id}],
avatar_url: "legacy.png",
};
assert.equal(people.pm_with_url(message), "#narrow/dm/302-Maria-Athens");
assert.equal(people.pm_perma_link(message), "#narrow/dm/30,302-dm");
assert.equal(people.small_avatar_url(message), "legacy.png");
message = {
avatar_url: undefined,
sender_id: maria.user_id,
};
assert.equal(
people.small_avatar_url(message),
"https://secure.gravatar.com/avatar/6dbdd7946b58d8b11351fcb27e5cdd55?d=identicon",
);
// No gravatar url cached yet
message = {
avatar_url: undefined,
sender_id: leo.user_id,
};
assert.equal(
people.small_avatar_url(message),
"https://secure.gravatar.com/avatar/ce9581fbf1beefbad43a4233aa65954c?d=identicon",
);
blueslip.expect("error", "Unknown user_id in maybe_get_user_by_id");
message = {
avatar_url: undefined,
sender_email: "foo@example.com",
sender_id: 9999999,
};
assert.equal(
people.small_avatar_url(message),
"https://secure.gravatar.com/avatar/b48def645758b95537d4424c84d1a9ff?d=identicon",
);
message = {
sender_id: ashton.user_id,
};
assert.equal(people.small_avatar_url(message), `/avatar/${ashton.user_id}`);
message = {
type: "private",
display_recipient: [{id: me.user_id}],
};
assert.equal(people.pm_with_url(message), "#narrow/dm/30-Me-Myself");
assert.equal(people.pm_perma_link(message), "#narrow/dm/30-dm");
message = {type: "stream"};
assert.equal(people.pm_with_user_ids(message), undefined);
assert.equal(people.all_user_ids_in_pm(message), undefined);
// Test undefined user_ids
assert.equal(people.pm_reply_user_string(message), undefined);
assert.equal(people.pm_with_url(message), undefined);
// Test sender_is_bot
const bot = bot_botson;
people.add_active_user(bot);
message = {sender_id: bot.user_id};
assert.equal(people.sender_is_bot(message), true);
message = {sender_id: maria.user_id};
assert.equal(people.sender_is_bot(message), false);
message = {sender_id: undefined};
assert.equal(people.sender_is_bot(message), false);
// Test sender_is_guest
people.add_active_user(guest);
message = {sender_id: guest.user_id};
assert.equal(people.sender_is_guest(message), true);
message = {sender_id: maria.user_id};
assert.equal(people.sender_is_guest(message), false);
message = {sender_id: charles.user_id};
assert.equal(people.sender_is_guest(message), false);
message = {sender_id: undefined};
assert.equal(people.sender_is_guest(message), false);
// Test sender_is_deactivated
people.deactivate(maria);
message = {sender_id: maria.user_id};
assert.equal(people.sender_is_deactivated(message), true);
message = {sender_id: charles.user_id};
assert.equal(people.sender_is_deactivated(message), false);
message = {sender_id: undefined};
assert.equal(people.sender_is_deactivated(message), false);
});
run_test("extract_people_from_message", () => {
initialize();
const message = {
type: "stream",
sender_full_name: maria.full_name,
sender_id: maria.user_id,
sender_email: maria.email,
};
assert.ok(!people.is_known_user_id(maria.user_id));
blueslip.expect("error", "Added user late");
people.extract_people_from_message(message);
assert.ok(people.is_known_user_id(maria.user_id));
blueslip.reset();
});
run_test("get_people_for_search_bar", ({override}) => {
initialize();
let user_ids;
override(message_user_ids, "user_ids", () => user_ids);
for (const i of _.range(20)) {
const person = make_user({
email: "whatever@email.com",
full_name: "James Jones",
user_id: 1000 + i,
});
people.add_active_user(person);
}
user_ids = [];
const big_results = people.get_people_for_search_bar("James");
assert.equal(big_results.length, 20);
user_ids = [1001, 1002, 1003, 1004, 1005, 1006];
const small_results = people.get_people_for_search_bar("Jones");
// As long as there are 5+ results among the user_ids
// in message_user_ids, we will get a small result and not
// search all people.
assert.equal(small_results.length, 6);
});
run_test("updates", () => {
initialize();
const old_email = "FOO@example.com";
const new_email = "bar@example.com";
const user_id = 502;
let person = make_user({
email: old_email,
user_id,
full_name: "Foo Barson",
});
people.add_active_user(person);
// Do sanity checks on our data.
assert.equal(people.get_by_email(old_email).user_id, user_id);
assert.equal(people.get_by_email(new_email), undefined);
// DO THE EMAIL UPDATE HERE.
people.update_email(user_id, new_email);
// Now look up using the new email.
assert.equal(people.get_by_email(new_email).user_id, user_id);
const all_people = get_all_persons();
assert.equal(all_people.length, 3);
person = all_people.find((p) => p.email === new_email);
assert.equal(person.full_name, "Foo Barson");
// Test shim where we can still retrieve user info using the
// old email.
blueslip.expect(
"warn",
"Obsolete email passed to get_by_email: FOO@example.com new email = bar@example.com",
);
person = people.get_by_email(old_email);
assert.equal(person.user_id, user_id);
});
run_test("track_duplicate_full_names", () => {
initialize();
people.add_active_user(maria);
people.add_active_user(stephen1);
assert.ok(!people.is_duplicate_full_name("Stephen King"));
assert.equal(people.get_user_id_from_name("Stephen King"), stephen1.user_id);
// Now duplicate the Stephen King name.
people.add_active_user({...stephen2});
// For duplicate names we won't try to guess which
// user_id the person means; the UI should use
// other codepaths for disambiguation.
assert.equal(people.get_user_id_from_name("Stephen King"), undefined);
assert.ok(people.is_duplicate_full_name("Stephen King"));
assert.ok(!people.is_duplicate_full_name("Maria Athens"));
assert.ok(!people.is_duplicate_full_name("Some Random Name"));
// It is somewhat janky that we have to clone
// stephen2 here. It would be nice if people.set_full_name
// just took a user_id as the first parameter.
people.set_full_name({...stephen2}, "Stephen King JP");
assert.ok(!people.is_duplicate_full_name("Stephen King"));
assert.ok(!people.is_duplicate_full_name("Stephen King JP"));
});
run_test("get_mention_syntax", () => {
initialize();
// blueslip warning is not raised for wildcard mentions without a user_id
assert.equal(people.get_mention_syntax("all"), "@**all**");
assert.equal(people.get_mention_syntax("everyone", undefined, true), "@_**everyone**");
assert.equal(people.get_mention_syntax("stream"), "@**channel**");
assert.equal(people.get_mention_syntax("channel"), "@**channel**");
assert.equal(people.get_mention_syntax("topic"), "@**topic**");
people.add_active_user(stephen1);
people.add_active_user(stephen2);
people.add_active_user(maria);
assert.ok(people.is_duplicate_full_name("Stephen King"));
blueslip.expect("warn", "get_mention_syntax called without user_id.");
assert.equal(people.get_mention_syntax("Stephen King"), "@**Stephen King**");
blueslip.reset();
assert.equal(people.get_mention_syntax("Stephen King", 601), "@**Stephen King|601**");
assert.equal(people.get_mention_syntax("Stephen King", 602, true), "@_**Stephen King|602**");
assert.equal(people.get_mention_syntax("Maria Athens", 603), "@**Maria Athens**");
// With always_include_id, the id is pinned even when the name is unique.
assert.equal(
people.get_mention_syntax("Maria Athens", 603, false, true),
"@**Maria Athens|603**",
);
// Following tests handle a special case when `full_name` matches with a wildcard.
//
// At this point, there is no duplicate full name, `all`, so we should still get
// mention syntax with `user_id` appended to it.
people.add_active_user(all1);
assert.equal(people.get_mention_syntax("all", 1202), "@**all|1202**");
people.add_active_user(all2);
assert.ok(people.is_duplicate_full_name("all"));
assert.equal(people.get_mention_syntax("all", 1203, true), "@_**all|1203**");
});
run_test("initialize", () => {
initialize();
people.init();
const params = {};
const retiree = make_user({
email: "retiree@example.com",
user_id: 15,
full_name: "Retiree",
});
params.realm_non_active_users = [retiree];
const current_user = make_user({
email: "my_email@example.com",
user_id: 42,
full_name: "Me Myself",
});
const alice = make_user({
email: "alice@example.com",
user_id: 16,
full_name: "Alice",
});
params.realm_users = [alice, current_user];
const test_bot = make_user({
email: "bot@example.com",
user_id: 17,
full_name: "Test Bot",
});
params.cross_realm_bots = [test_bot];
const user_group_params = {
realm_user_groups: [
make_user_group({
is_system_group: true,
members: [42, 17, 16, 15],
}),
],
};
people.initialize(current_user.user_id, params, user_group_params);
assert.equal(people.is_active_user_or_system_bot(17), true);
assert.ok(people.is_valid_user_id_for_compose(test_bot.user_id));
assert.ok(people.is_valid_user_id_for_compose(alice.user_id));
assert.ok(people.is_valid_user_id_for_compose(retiree.user_id));
blueslip.expect("error", "Unknown user_id in maybe_get_user_by_id");
assert.ok(!people.is_valid_user_id_for_compose(9999));
assert.ok(people.is_valid_bulk_user_ids_for_compose([17, 16, 15]));
blueslip.reset();
blueslip.expect("error", "Unknown user_id in maybe_get_user_by_id");
assert.ok(!people.is_valid_bulk_user_ids_for_compose([17, 9999, 15], false));
blueslip.reset();
assert.ok(people.is_my_user_id(42));
const fetched_retiree = people.get_by_user_id(15);
assert.equal(fetched_retiree.full_name, "Retiree");
assert.equal(page_params.realm_users, undefined);
assert.equal(page_params.cross_realm_bots, undefined);
assert.equal(page_params.realm_non_active_users, undefined);
});
run_test("predicate_for_user_settings_filters", ({override}) => {
initialize();
/*
This function calls matches_user_settings_search,
so that is where we do more thorough testing.
This test is just a sanity check for now.
*/
override(current_user, "is_admin", false);
const fred_smith = {full_name: "Fred Smith", role: Role.OWNER};
// Test only when text_search filter is true
assert.equal(
people.predicate_for_user_settings_filters(fred_smith, {text_search: "fr", role_code: 0}),
true,
);
// Test only when role_code filter is true
assert.equal(
people.predicate_for_user_settings_filters(fred_smith, {text_search: "", role_code: 100}),
true,
);
// Test only when text_search filter is false
assert.equal(
people.predicate_for_user_settings_filters(fred_smith, {text_search: "ab", role_code: 0}),
false,
);
// Test only when role_code filter is false
assert.equal(
people.predicate_for_user_settings_filters(fred_smith, {text_search: "", role_code: 200}),
false,
);
// Test when both text_search and role_code filter are true
assert.equal(
people.predicate_for_user_settings_filters(fred_smith, {
text_search: "smi",
role_code: 100,
}),
true,
);
// Test when both text_search and role_code filter are false
assert.equal(
people.predicate_for_user_settings_filters(fred_smith, {text_search: "de", role_code: 300}),
false,
);
});
run_test("matches_user_settings_search", ({override}) => {
initialize();
const match = people.matches_user_settings_search;
override(current_user, "is_admin", false);
assert.equal(match({email: "fred@example.com"}, "fred"), false);
assert.equal(match({full_name: "Fred Smith"}, "fr"), true);
override(current_user, "is_admin", true);
assert.equal(match({delivery_email: "fred@example.com"}, "fr"), true);
assert.equal(
match(
{
delivery_email: "bogus",
email: "fred@example.com",
},
"fr",
),
false,
);
assert.equal(match({delivery_email: "fred@example.com"}, "fr"), true);
assert.equal(match({email: "fred@example.com"}, "fr"), false);
// test normal stuff
assert.equal(match({email: "fred@example.com"}, "st"), false);
assert.equal(match({full_name: "Fred Smith"}, "st"), false);
assert.equal(match({full_name: "Joe Frederick"}, "st"), false);
assert.equal(match({delivery_email: "fred@example.com"}, "fr"), true);
assert.equal(match({full_name: "Fred Smith"}, "fr"), true);
assert.equal(match({full_name: "Joe Frederick"}, "fr"), true);
// test in-string matches...we may want not to be so liberal
// here about matching, as it's noisy for large realms (who
// need search the most)
assert.equal(match({delivery_email: "fred@example.com"}, "re"), true);
assert.equal(match({full_name: "Fred Smith"}, "re"), true);
assert.equal(match({full_name: "Joe Frederick"}, "re"), true);
});
run_test("is_valid_full_name_and_user_id", () => {
initialize();
assert.ok(!people.is_valid_full_name_and_user_id("bogus", 99));
assert.ok(!people.is_valid_full_name_and_user_id(me.full_name, 99));
assert.ok(people.is_valid_full_name_and_user_id(me.full_name, me.user_id));
});
run_test("get_visible_email", () => {
initialize();
people.add_active_user(steven);
people.add_active_user(maria);
let email = people.get_visible_email(steven);
assert.equal(email, steven.delivery_email);
email = people.get_visible_email(maria);
assert.equal(email, maria.email);
});
run_test("get_active_message_people", () => {
initialize();
message_user_ids.user_ids = () => [steven.user_id, maria.user_id, alice1.user_id];
people.add_active_user(steven);
people.add_active_user(maria);
people.add_active_user(alice1);
let active_message_people = people.get_active_message_people();
assert.deepEqual(active_message_people, [steven, maria, alice1]);
people.deactivate(alice1);
active_message_people = people.get_active_message_people();
assert.deepEqual(active_message_people, [steven, maria]);
});
run_test("direct_message_group_string", () => {
initialize();
assert.equal(people.direct_message_group_string({type: "stream"}), undefined);
function direct_message_group(user_ids) {
return people.direct_message_group_string({
type: "private",
display_recipient: user_ids.map((id) => ({id})),
});
}
people.add_active_user(maria);
people.add_active_user(bob);
assert.equal(direct_message_group([]), undefined);
assert.equal(direct_message_group([me.user_id, maria.user_id]), undefined);
assert.equal(direct_message_group([me.user_id, maria.user_id, bob.user_id]), "203,302");
});
run_test("get_realm_active_human_users", () => {
initialize();
let humans = people.get_realm_active_human_users();
assert.equal(humans.length, 1);
assert.deepEqual(humans, [me]);
people.add_active_user(maria);
people.add_active_user(bot_botson);
humans = people.get_realm_active_human_users();
assert.equal(humans.length, 2);
assert.deepEqual(humans, [me, maria]);
people.deactivate(maria);
humans = people.get_realm_active_human_users();
assert.equal(humans.length, 1);
assert.deepEqual(humans, [me]);
});
run_test("should_show_guest_user_indicator", ({override}) => {
initialize();
people.add_active_user(charles);
people.add_active_user(guest);
override(realm, "realm_enable_guest_user_indicator", false);
assert.equal(people.should_add_guest_user_indicator(charles.user_id), false);
assert.equal(people.should_add_guest_user_indicator(guest.user_id), false);
override(realm, "realm_enable_guest_user_indicator", true);
assert.equal(people.should_add_guest_user_indicator(charles.user_id), false);
assert.equal(people.should_add_guest_user_indicator(guest.user_id), true);
});
run_test("get_user_by_id_assert_valid", ({override}) => {
initialize();
people.add_active_user(charles);
const inaccessible_user_id = 99;
override(realm, "realm_bot_domain", "zulipdev.com");
override(settings_data, "user_can_access_all_other_users", () => false);
let user = people.get_user_by_id_assert_valid(inaccessible_user_id);
assert.equal(user.full_name, "translated: Unknown user");
assert.equal(user.user_id, inaccessible_user_id);
assert.ok(user.is_inaccessible_user);
assert.equal(user.email, "user99@zulipdev.com");
user = people.get_user_by_id_assert_valid(charles.user_id);
assert.equal(user.full_name, charles.full_name);
assert.ok(!user.is_inaccessible_user);
assert.equal(user.email, charles.email);
override(settings_data, "user_can_access_all_other_users", () => true);
assert.throws(
() => {
people.get_user_by_id_assert_valid(199);
},
{
name: "Error",
message: "Unknown user_id in get_by_user_id: 199",
},
);
user = people.get_user_by_id_assert_valid(charles.user_id);
assert.equal(user.full_name, charles.full_name);
assert.ok(!user.is_inaccessible_user);
assert.equal(user.email, charles.email);
});
run_test("user_can_initiate_direct_message_thread", ({override}) => {
initialize();
people.add_active_user(welcome_bot);
override(realm, "realm_direct_message_initiator_group", nobody.id);
assert.ok(!people.user_can_initiate_direct_message_thread("32"));
// Can send if only bots and self are present.
assert.ok(people.user_can_initiate_direct_message_thread("4,30"));
override(realm, "realm_direct_message_initiator_group", everyone.id);
assert.ok(people.user_can_initiate_direct_message_thread("32"));
});
run_test("sort_by_username", () => {
initialize();
people.add_active_user(maria);
people.add_active_user(cedar);
people.add_active_user(leo);
assert.deepEqual(
people.sort_user_ids_by_username([maria.user_id, cedar.user_id, leo.user_id]),
[cedar.user_id, leo.user_id, maria.user_id],
);
});
run_test("get_users_that_match_role_ids", () => {
people.add_active_user(realm_admin);
people.add_active_user(realm_owner);
people.add_active_user(moderator);
people.add_active_user(guest);
const user_ids = new Set([
realm_admin.user_id,
realm_owner.user_id,
moderator.user_id,
guest.user_id,
]);
let users = people.get_users_that_match_role_ids(
user_ids,
new Set([settings_config.user_role_values.admin.code]),
);
assert.deepEqual(new Set(users), new Set([realm_admin]));
users = people.get_users_that_match_role_ids(
user_ids,
new Set([settings_config.user_role_values.moderator.code]),
);
assert.deepEqual(new Set(users), new Set([moderator]));
users = people.get_users_that_match_role_ids(
user_ids,
new Set([
settings_config.user_role_values.admin.code,
settings_config.user_role_values.owner.code,
]),
);
assert.deepEqual(new Set(users), new Set([realm_owner, realm_admin]));
});
// reset to native Date()
run_test("reset MockDate", () => {
clock.reset();
});
run_test("user fetch retries a failed request", async ({override, override_rewire}) => {
initialize();
people.add_valid_user_id(1);
let retry_count = 1;
override(channel, "get", ({url, data, success, error}) => {
assert.equal(url, "/json/users");
assert.ok(data.user_ids.includes("1"));
// Simulate failure of the first two attempts.
if (retry_count < 3) {
retry_count += 1;
error({responseJSON: {msg: "test error"}});
return;
}
success({
members: [
{
email: "user1@example.com",
user_id: 1,
full_name: "First user",
delivery_email: "",
date_joined: "",
is_active: true,
is_owner: false,
is_admin: false,
is_guest: false,
role: 1,
avatar_url: "",
avatar_version: 1,
is_bot: false,
is_imported_stub: false,
},
],
result: "success",
msg: "",
});
});
// Math.round will be `0`.
override_rewire(retry_backoff, "get_retry_backoff_seconds", () => retry_count / 1000);
// Check that we retry the request after a failed attempt.
blueslip.expect(
"warn",
"Fetch for users failed, retrying after 0 seconds. Error: test error",
2,
);
await people.fetch_users_from_ids_internal([1]);
});
run_test("user fetch during initialize", async ({override}) => {
initialize();
people.init();
// Valid users missing from params data sent by server.
const users_in_response = [
{
email: "retiree@example.com",
user_id: 15,
full_name: "Retiree",
delivery_email: "",
date_joined: "",
is_active: true,
is_owner: false,
is_admin: false,
is_guest: false,
role: 1,
avatar_url: "",
avatar_version: 1,
is_bot: false,
is_imported_stub: false,
},
{
email: "alice@example.com",
user_id: 16,
full_name: "Alice",
delivery_email: "",
date_joined: "",
is_active: false,
is_owner: false,
is_admin: false,
is_guest: false,
role: 1,
avatar_url: "",
avatar_version: 1,
is_bot: false,
is_imported_stub: false,
},
];
const params = {
realm_users: [
{
email: "my_email@example.com",
user_id: 42,
full_name: "Me Myself",
},
],
realm_non_active_users: [],
cross_realm_bots: [
{
email: "bot@example.com",
user_id: 17,
full_name: "Test Bot",
},
],
};
const user_group_params = {
realm_user_groups: [
make_user_group({
is_system_group: true,
members: [42, 17, 16, 15],
}),
],
};
const my_user_id = 42;
let user_GET_request_calls = 0;
let promise_for_user_already_in_transit;
override(channel, "get", ({url, data, success, _error}) => {
user_GET_request_calls += 1;
assert.equal(url, "/json/users");
assert.ok(data.user_ids.includes("15"));
assert.ok(data.user_ids.includes("16"));
assert.ok(!data.user_ids.includes("42"));
assert.ok(!data.user_ids.includes("17"));
// While this get request is in-flight, request for the
// a subset of the users again and check if it resolved.
promise_for_user_already_in_transit = people.get_or_fetch_users_from_ids([15]);
success({
members: users_in_response,
result: "success",
msg: "",
});
});
additional_calls_before_set_timeout = () => {
// This fetch request doesn't result in any new GET request,
// since the request for this user is added to pending users
// for fetch and returns a promise.
const promise = people.fetch_users_from_ids_internal([15]);
assert.ok(promise instanceof Promise);
};
await people.initialize(my_user_id, params, user_group_params);
await promise_for_user_already_in_transit;
assert.equal(user_GET_request_calls, 1);
const retiree = people.get_by_user_id(15);
const alice = people.get_by_user_id(16);
assert.equal(retiree.full_name, "Retiree");
assert.equal(alice.full_name, "Alice");
assert.ok(people.is_valid_user_id(15));
assert.ok(people.is_valid_user_id(16));
assert.ok(people.is_valid_user_id(42));
assert.ok(people.is_valid_user_id(17));
assert.ok(people.is_valid_user_ids([15, 16, 42, 17]));
assert.ok(!people.is_valid_user_ids([15, 16, 42, 9999]));
assert.equal(people.get_by_email("alice@example.com").user_id, 16);
assert.equal(people.get_by_email("retiree@example.com").user_id, 15);
blueslip.expect("error", "Ignored invalid user_ids: 1, 2");
assert.deepEqual(await people.get_or_fetch_users_from_ids([1, 2]), []);
assert.equal(user_GET_request_calls, 1);
});
function make_fetched_user(user_id) {
return {
email: `user${user_id}@example.com`,
user_id,
full_name: `User ${user_id}`,
delivery_email: "",
date_joined: "",
is_active: true,
is_owner: false,
is_admin: false,
is_guest: false,
role: Role.MEMBER,
avatar_url: "",
avatar_version: 1,
is_bot: false,
is_imported_stub: false,
};
}
function make_params_for_users_missing_from_register(my_user_id, missing_user_ids) {
return {
people_params: {
realm_users: [
{
email: "my_email@example.com",
user_id: my_user_id,
full_name: "Me Myself",
},
],
realm_non_active_users: [],
cross_realm_bots: [],
},
user_group_params: {
realm_user_groups: [
make_user_group({
is_system_group: true,
members: [my_user_id, ...missing_user_ids],
}),
],
},
};
}
run_test("user fetch batches a large request", async ({override}) => {
initialize();
people.init();
const my_user_id = 42;
const user_ids_to_fetch = Array.from({length: 1200}, (_unused, index) => 1000 + index);
const {people_params, user_group_params} = make_params_for_users_missing_from_register(
my_user_id,
user_ids_to_fetch,
);
const requested_batches = [];
override(channel, "get", ({url, data, success}) => {
assert.equal(url, "/json/users");
const batch = JSON.parse(data.user_ids);
// Users from the earlier batches are already known by the time we
// request this one, rather than only once every batch has arrived.
assert.ok(requested_batches.flat().every((user_id) => people.is_known_user_id(user_id)));
requested_batches.push(batch);
success({
members: batch.map((user_id) => make_fetched_user(user_id)),
result: "success",
msg: "",
});
});
await people.initialize(my_user_id, people_params, user_group_params);
assert.deepEqual(
requested_batches.map((batch) => batch.length),
[500, 500, 200],
);
assert.deepEqual(
requested_batches.flat().toSorted((a, b) => a - b),
user_ids_to_fetch,
);
for (const user_id of user_ids_to_fetch) {
assert.equal(people.get_by_user_id(user_id).full_name, `User ${user_id}`);
}
});
run_test("user fetch resumes after a failed batch", async ({override, override_rewire}) => {
// Inaccessible users make a batch succeed while returning no members,
// which is what requires tracking progress by the IDs we sent.
initialize();
people.init();
override(settings_data, "user_can_access_all_other_users", () => false);
const my_user_id = 42;
const inaccessible_user_ids = Array.from({length: 1200}, (_unused, index) => 1000 + index);
const {people_params, user_group_params} = make_params_for_users_missing_from_register(
my_user_id,
inaccessible_user_ids,
);
const response_with_no_accessible_users = {members: [], result: "success", msg: ""};
const requested_batches = [];
// Requests 2 and 4 are the first attempts at the second and third
// batches, so a success falls between the two failures.
const failing_request_numbers = new Set([2, 4]);
override(channel, "get", ({url, data, success, error}) => {
assert.equal(url, "/json/users");
requested_batches.push(JSON.parse(data.user_ids));
if (failing_request_numbers.has(requested_batches.length)) {
error({responseJSON: {msg: "test error"}});
return;
}
success(response_with_no_accessible_users);
});
const backoff_attempt_counts = [];
override_rewire(retry_backoff, "get_retry_backoff_seconds", (_xhr, attempts) => {
backoff_attempt_counts.push(attempts);
return 0;
});
blueslip.expect(
"warn",
"Fetch for users failed, retrying after 0 seconds. Error: test error",
2,
);
await people.initialize(my_user_id, people_params, user_group_params);
// Only the failed batches are requested twice; the rest are not refetched.
assert.deepEqual(
requested_batches.map((batch) => batch.length),
[500, 500, 500, 200, 200],
);
assert.deepEqual(requested_batches[1], requested_batches[2]);
assert.deepEqual(requested_batches[3], requested_batches[4]);
const request_count_by_user_id = new Map();
for (const user_id of requested_batches.flat()) {
request_count_by_user_id.set(user_id, (request_count_by_user_id.get(user_id) ?? 0) + 1);
}
assert.ok(requested_batches[0].every((user_id) => request_count_by_user_id.get(user_id) === 1));
// The counter restarts at 1 because a batch succeeded between the failures.
assert.deepEqual(backoff_attempt_counts, [1, 1]);
for (const user_id of inaccessible_user_ids) {
assert.ok(people.get_by_user_id(user_id).is_inaccessible_user);
}
});
run_test("user fetch corner case", async ({override, override_rewire}) => {
// We test here for the following sequence of fetches:
// 1st request for [1, 2].
// 2nd request for [1, 2, 3] while the first one is in-flight.
// 3rd request for [3] while the first one is in-flight.
//
// We should end up with two GET requests:
// - The first for [1, 2].
// - The second for [3].
//
// If the request for [3] finished first, we should resolve the 3rd
// request without waiting for the 1st request to finish.
initialize();
people.add_valid_user_id(1);
people.add_valid_user_id(2);
people.add_valid_user_id(3);
const first_request_response = [
{
email: "retiree@example.com",
user_id: 1,
full_name: "Retiree",
delivery_email: "",
date_joined: "",
is_active: true,
is_owner: false,
is_admin: false,
is_guest: false,
role: 1,
avatar_url: "",
avatar_version: 1,
is_bot: false,
is_imported_stub: false,
},
{
email: "alice@example.com",
user_id: 2,
full_name: "Alice",
delivery_email: "",
date_joined: "",
is_active: false,
is_owner: false,
is_admin: false,
is_guest: false,
role: 1,
avatar_url: "",
avatar_version: 1,
is_bot: false,
is_imported_stub: false,
},
];
const second_request_response = [
{
email: "third@example.com",
user_id: 3,
full_name: "Third user",
delivery_email: "",
date_joined: "",
is_active: true,
is_owner: false,
is_admin: false,
is_guest: false,
role: 1,
avatar_url: "",
avatar_version: 1,
is_bot: false,
is_imported_stub: false,
},
];
let sent_success_response_for_third_user = false;
override(channel, "get", ({url, data, success, error}) => {
assert.equal(url, "/json/users");
// There shouldn't be a fetch for [1, 2, 3].
assert.ok(data.user_ids !== "[1,2,3]");
if (data.user_ids === "[3]") {
sent_success_response_for_third_user = true;
success({
members: second_request_response,
result: "success",
msg: "",
});
return;
}
if (data.user_ids === "[1,2]") {
if (!sent_success_response_for_third_user) {
error({responseJSON: {msg: "Network error"}});
return;
}
success({
members: first_request_response,
result: "success",
msg: "",
});
return;
}
});
override_rewire(retry_backoff, "get_retry_backoff_seconds", () => 0);
// Check that we retry the request after a failed attempt.
blueslip.expect(
"warn",
"Fetch for users failed, retrying after 0 seconds. Error: Network error",
);
// We need to check that third promise resolves before the first promise.
let third_promise_resolved = false;
let first_promise_resolved = false;
await Promise.all([
(async () => {
await people.get_or_fetch_users_from_ids([1, 2]);
first_promise_resolved = true;
assert.ok(third_promise_resolved);
})(),
(async () => {
await people.get_or_fetch_users_from_ids([1, 2, 3]);
assert.ok(first_promise_resolved);
assert.ok(third_promise_resolved);
})(),
(async () => {
await people.get_or_fetch_users_from_ids([3]);
third_promise_resolved = true;
assert.ok(!first_promise_resolved);
})(),
]);
const user1 = people.get_by_user_id(1);
const user2 = people.get_by_user_id(2);
const user3 = people.get_by_user_id(3);
assert.equal(user1.full_name, "Retiree");
assert.equal(user2.full_name, "Alice");
assert.equal(user3.full_name, "Third user");
});
run_test("fetch inaccessible user", async ({override, override_rewire}) => {
initialize();
override_rewire(people, "fetch_users_from_ids_internal", noop);
people.add_valid_user_id(1);
override(settings_data, "user_can_access_all_other_users", () => false);
const [inaccessible_user] = await people.get_or_fetch_users_from_ids([1]);
assert.equal(inaccessible_user.user_id, 1);
assert.equal(inaccessible_user.is_inaccessible_user, true);
});
run_test("get_by_user_id", () => {
initialize();
people.add_active_user(maria);
const user = people.get_by_user_id(maria.user_id);
assert.equal(user.full_name, maria.full_name);
assert.throws(
() => {
people.get_by_user_id(9999);
},
{
name: "Error",
message: "Unknown user_id in get_by_user_id: 9999",
},
);
blueslip.expect("error", "User ID: 8888 is valid but not found in people_by_user_id_dict");
people.add_valid_user_id(8888);
assert.throws(
() => {
people.get_by_user_id(8888);
},
{
name: "Error",
message: "Unknown user_id in get_by_user_id: 8888",
},
);
});
run_test("is_valid_bot_user", () => {
initialize();
people.add_active_user(bot_botson);
people.add_active_user(isaac);
assert.equal(people.is_valid_bot_user(bot_botson.user_id), true);
assert.equal(people.is_valid_bot_user(isaac.user_id), false);
assert.equal(people.is_valid_bot_user(me.user_id), false);
// Unknown user_id returns false without error (uses ignore_missing).
assert.equal(people.is_valid_bot_user(9999), false);
});
run_test("maybe_get_user_by_id", () => {
initialize();
people.add_active_user(isaac);
const user = people.maybe_get_user_by_id(isaac.user_id);
assert.equal(user.full_name, "Isaac Newton");
// With ignore_missing=true, returns undefined without error.
assert.equal(people.maybe_get_user_by_id(9999, true), undefined);
// Without ignore_missing, logs an error and returns undefined.
blueslip.expect("error", "Unknown user_id in maybe_get_user_by_id");
assert.equal(people.maybe_get_user_by_id(9999), undefined);
});
run_test("small_avatar_url_for_person", () => {
initialize();
people.add_active_user(charles);
people.add_active_user(maria);
// User with an explicit avatar_url returns it directly.
assert.equal(people.small_avatar_url_for_person(charles), "http://charles.com/foo.png");
// Current user (me) has no avatar_url set, so falls back to /avatar/{user_id}.
assert.equal(people.small_avatar_url_for_person(me), `/avatar/${me.user_id}`);
// User with avatar_url=null computes a gravatar URL from email.
assert.equal(
people.small_avatar_url_for_person(maria),
"https://secure.gravatar.com/avatar/6dbdd7946b58d8b11351fcb27e5cdd55?d=identicon",
);
// Verify the gravatar URL was cached on the person object.
assert.equal(
maria.avatar_url,
"https://secure.gravatar.com/avatar/6dbdd7946b58d8b11351fcb27e5cdd55?d=identicon",
);
});
run_test("sender_is_bot", () => {
initialize();
people.add_active_user(bot_botson);
people.add_active_user(isaac);
assert.equal(people.sender_is_bot({sender_id: bot_botson.user_id}), true);
assert.equal(people.sender_is_bot({sender_id: isaac.user_id}), false);
assert.equal(people.sender_is_bot({sender_id: me.user_id}), false);
assert.equal(people.sender_is_bot({sender_id: undefined}), false);
});
run_test("sender_is_guest", () => {
initialize();
people.add_active_user(guest);
people.add_active_user(isaac);
assert.equal(people.sender_is_guest({sender_id: guest.user_id}), true);
assert.equal(people.sender_is_guest({sender_id: isaac.user_id}), false);
assert.equal(people.sender_is_guest({sender_id: me.user_id}), false);
assert.equal(people.sender_is_guest({sender_id: undefined}), false);
});
run_test("get_user_mentions_for_display", () => {
people.add_active_user(stephen1);
people.add_active_user(stephen2);
people.add_active_user(maria);
assert.equal(people.get_user_mentions_for_display([stephen1]), "@**Stephen King|601**");
assert.equal(
people.get_user_mentions_for_display([stephen1, stephen2]),
"@**Stephen King|601** and @**Stephen King|602**",
);
// User mentions are sorted.
assert.equal(
people.get_user_mentions_for_display([stephen1, stephen2, maria]),
"@**Maria Athens**, @**Stephen King|601**, and @**Stephen King|602**",
);
assert.equal(
people.get_user_mentions_for_display([stephen1, stephen2, maria], true),
"@_**Maria Athens**, @_**Stephen King|601**, and @_**Stephen King|602**",
);
});