Files
upstream-rocketchat/apps/meteor/tests/end-to-end/api/status-visibility.ts
upstream-mirror 30810eecd7
Some checks failed
Create issues from TODOs / todos (push) Failing after 3s
Close inactive issues / close-issues (push) Has been cancelled
Deploy GitHub Pages / deploy-preview (push) Has been cancelled
CI / ⚙️ Variables Setup (push) Has been cancelled
CI / ⚙️ Test Guard (push) Has been cancelled
CI / 🚀 Notify external services - draft (push) Has been cancelled
CI / 📦 Build Packages (push) Has been cancelled
CI / 📦 Meteor Build (coverage) (push) Has been cancelled
CI / 📦 Meteor Build (production) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], fips) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Publish Docker Images (ghcr.io) (push) Has been cancelled
CI / 📦 Track Image Sizes (push) Has been cancelled
CI / 🔎 Code Check - Action Lint (push) Has been cancelled
CI / 🔎 Code Check (push) Has been cancelled
CI / 🔨 Test Storybook (push) Has been cancelled
CI / 🔨 Test Unit (push) Has been cancelled
CI / 🔨 Test API (CE) (push) Has been cancelled
CI / 🔨 Test API Livechat (CE) (push) Has been cancelled
CI / 🔨 Test UI (CE) (push) Has been cancelled
CI / 🔨 Test API (EE) (push) Has been cancelled
CI / 🔨 Test API Livechat (EE) (push) Has been cancelled
CI / 🔨 Test API Apps (deno-runtime - EE) (push) Has been cancelled
CI / 🔨 Test UI (EE) (push) Has been cancelled
CI / 🔨 Test API (FIPS) (push) Has been cancelled
CI / 🔨 Test API Livechat (FIPS) (push) Has been cancelled
CI / 🔨 Test UI (FIPS) (push) Has been cancelled
CI / 🔨 Test Federation Matrix (push) Has been cancelled
CI / 📊 Report Coverage (push) Has been cancelled
CI / ✅ Tests Done (push) Has been cancelled
CI / 🚀 Publish build assets (push) Has been cancelled
CI / 🚀 Publish Docker Images (DockerHub) (push) Has been cancelled
CI / 🚀 Notify external services (push) Has been cancelled
CI / Update Version Durability (push) Has been cancelled
Release / Release (push) Has been cancelled
Code scanning - action / CodeQL-Build (push) Failing after 2s
Auto-close duplicate issues / auto-close-duplicates (push) Failing after 1s
snapshot of https://github.com/RocketChat/Rocket.Chat.git (develop, 2026-09-16; blobs > 131072B omitted)
2026-09-16 01:21:02 +05:00

374 lines
12 KiB
TypeScript

import type { Credentials } from '@rocket.chat/api-client';
import type { IRoom, ITeam, IUser } from '@rocket.chat/core-typings';
import { TeamType, UserStatus } from '@rocket.chat/core-typings';
import { expect } from 'chai';
import { after, before, describe, it } from 'mocha';
import { api, credentials, getCredentials, request } from '../../data/api-data';
import { updateEESetting } from '../../data/permissions.helper';
import { createRoom, deleteRoom } from '../../data/rooms.helper';
import { createTeam, deleteTeam } from '../../data/teams.helper';
import { password } from '../../data/user';
import { createUser, deleteUser, login, setUserStatus } from '../../data/users.helper';
import { IS_EE } from '../../e2e/config/constants';
(IS_EE ? describe : describe.skip)('[Status Visibility] (Enterprise Only)', function () {
this.retries(0);
let hider: IUser & { username: string };
let viewer: IUser & { username: string };
let bystander: IUser & { username: string };
let hiderCredentials: Credentials;
let viewerCredentials: Credentials;
let bystanderCredentials: Credentials;
let channel: IRoom;
let group: IRoom;
let team: ITeam;
const usernamesOf = (members: { username?: string }[]) => members.map((member) => member.username);
const statusOf = (members: { username?: string; status?: string }[], username: string) =>
members.find((member) => member.username === username)?.status;
before((done) => getCredentials(done));
before(async () => {
await updateEESetting('Accounts_StatusVisibility_Enabled', true);
[hider, viewer, bystander] = (await Promise.all([
createUser({ joinDefaultChannels: false }),
createUser({ joinDefaultChannels: false }),
createUser({ joinDefaultChannels: false }),
])) as (IUser & { username: string })[];
[hiderCredentials, viewerCredentials, bystanderCredentials] = await Promise.all([
login(hider.username, password),
login(viewer.username, password),
login(bystander.username, password),
]);
await setUserStatus(hiderCredentials, UserStatus.ONLINE);
await request
.post(api('users.setPreferences'))
.set(hiderCredentials)
.send({ data: { statusVisibilityDenied: [viewer.username] } })
.expect(200);
const members = [hider.username, viewer.username, bystander.username];
channel = (await createRoom({ type: 'c', name: `status-visibility-c-${Date.now()}`, members, credentials })).body.channel;
group = (await createRoom({ type: 'p', name: `status-visibility-p-${Date.now()}`, members, credentials })).body.group;
team = await createTeam(credentials, `status-visibility-t-${Date.now()}`, TeamType.PUBLIC, members);
});
after(async () => {
await Promise.all([deleteRoom({ type: 'c', roomId: channel._id }), deleteRoom({ type: 'p', roomId: group._id })]);
await deleteTeam(credentials, team.name);
await Promise.all([deleteUser(hider), deleteUser(viewer), deleteUser(bystander)]);
await updateEESetting('Accounts_StatusVisibility_Enabled', false);
});
describe('[/rooms.membersOrderedByRole]', () => {
it('should not return the hider when the viewer filters by online', async () => {
await request
.get(api('rooms.membersOrderedByRole'))
.set(viewerCredentials)
.query({ 'roomId': channel._id, 'status[]': UserStatus.ONLINE })
.expect(200)
.expect((res) => {
expect(usernamesOf(res.body.members)).to.not.include(hider.username);
expect(res.body.total).to.be.equal(res.body.members.length);
});
});
it('should return the hider as online to a user they do not hide from', async () => {
await request
.get(api('rooms.membersOrderedByRole'))
.set(bystanderCredentials)
.query({ 'roomId': channel._id, 'status[]': UserStatus.ONLINE })
.expect(200)
.expect((res) => {
expect(statusOf(res.body.members, hider.username)).to.be.equal(UserStatus.ONLINE);
});
});
it('should return the hider as offline when the viewer does not filter', async () => {
await request
.get(api('rooms.membersOrderedByRole'))
.set(viewerCredentials)
.query({ roomId: channel._id })
.expect(200)
.expect((res) => {
expect(usernamesOf(res.body.members)).to.include(hider.username);
expect(statusOf(res.body.members, hider.username)).to.be.equal(UserStatus.OFFLINE);
});
});
it('should return the hider when the viewer filters by offline', async () => {
await request
.get(api('rooms.membersOrderedByRole'))
.set(viewerCredentials)
.query({ 'roomId': channel._id, 'status[]': UserStatus.OFFLINE })
.expect(200)
.expect((res) => {
expect(usernamesOf(res.body.members)).to.include(hider.username);
expect(statusOf(res.body.members, hider.username)).to.be.equal(UserStatus.OFFLINE);
});
});
});
describe('[/channels.members]', () => {
it('should not return the hider when the viewer filters by online', async () => {
await request
.get(api('channels.members'))
.set(viewerCredentials)
.query({ 'roomId': channel._id, 'status[]': UserStatus.ONLINE })
.expect(200)
.expect((res) => {
expect(usernamesOf(res.body.members)).to.not.include(hider.username);
expect(res.body.total).to.be.equal(res.body.members.length);
});
});
it('should return the hider as online to a user they do not hide from', async () => {
await request
.get(api('channels.members'))
.set(bystanderCredentials)
.query({ 'roomId': channel._id, 'status[]': UserStatus.ONLINE })
.expect(200)
.expect((res) => {
expect(statusOf(res.body.members, hider.username)).to.be.equal(UserStatus.ONLINE);
});
});
});
describe('[/channels.online]', () => {
it('should not return the hider to the viewer', async () => {
await request
.get(api('channels.online'))
.set(viewerCredentials)
.query({ _id: channel._id })
.expect(200)
.expect((res) => {
expect(usernamesOf(res.body.online)).to.not.include(hider.username);
});
});
it('should return the hider to a user they do not hide from', async () => {
await request
.get(api('channels.online'))
.set(bystanderCredentials)
.query({ _id: channel._id })
.expect(200)
.expect((res) => {
expect(usernamesOf(res.body.online)).to.include(hider.username);
});
});
});
describe('[/groups.members]', () => {
it('should not return the hider when the viewer filters by online', async () => {
await request
.get(api('groups.members'))
.set(viewerCredentials)
.query({ 'roomId': group._id, 'status[]': UserStatus.ONLINE })
.expect(200)
.expect((res) => {
expect(usernamesOf(res.body.members)).to.not.include(hider.username);
expect(res.body.total).to.be.equal(res.body.members.length);
});
});
it('should return the hider as online to a user they do not hide from', async () => {
await request
.get(api('groups.members'))
.set(bystanderCredentials)
.query({ 'roomId': group._id, 'status[]': UserStatus.ONLINE })
.expect(200)
.expect((res) => {
expect(statusOf(res.body.members, hider.username)).to.be.equal(UserStatus.ONLINE);
});
});
});
describe('[/groups.online]', () => {
it('should not return the hider to the viewer', async () => {
await request
.get(api('groups.online'))
.set(viewerCredentials)
.query({ _id: group._id })
.expect(200)
.expect((res) => {
expect(usernamesOf(res.body.online)).to.not.include(hider.username);
});
});
it('should return the hider to a user they do not hide from', async () => {
await request
.get(api('groups.online'))
.set(bystanderCredentials)
.query({ _id: group._id })
.expect(200)
.expect((res) => {
expect(usernamesOf(res.body.online)).to.include(hider.username);
});
});
});
describe('[/teams.members]', () => {
const teamUsernamesOf = (members: { user: IUser }[]) => members.map(({ user }) => user.username);
it('should not return the hider when the viewer filters by online', async () => {
await request
.get(api('teams.members'))
.set(viewerCredentials)
.query({ 'teamId': team._id, 'status[]': UserStatus.ONLINE })
.expect(200)
.expect((res) => {
expect(teamUsernamesOf(res.body.members)).to.not.include(hider.username);
});
});
it('should return the hider to a user they do not hide from', async () => {
await request
.get(api('teams.members'))
.set(bystanderCredentials)
.query({ 'teamId': team._id, 'status[]': UserStatus.ONLINE })
.expect(200)
.expect((res) => {
expect(teamUsernamesOf(res.body.members)).to.include(hider.username);
});
});
it('should not expose the block list of any member', async () => {
await request
.get(api('teams.members'))
.set(viewerCredentials)
.query({ teamId: team._id })
.expect(200)
.expect((res) => {
res.body.members.forEach(({ user }: { user: IUser }) => {
expect(user.settings?.preferences ?? {}).to.not.have.property('statusVisibilityDenied');
});
});
});
});
describe('[/users.getStatus]', () => {
it('should report the hider as offline to the viewer', async () => {
await request
.get(api('users.getStatus'))
.set(viewerCredentials)
.query({ userId: hider._id })
.expect(200)
.expect((res) => {
expect(res.body.status).to.be.equal(UserStatus.OFFLINE);
expect(res.body).to.not.have.property('statusSource');
});
});
it('should report the real status to a user they do not hide from', async () => {
await request
.get(api('users.getStatus'))
.set(bystanderCredentials)
.query({ userId: hider._id })
.expect(200)
.expect((res) => {
expect(res.body.status).to.be.equal(UserStatus.ONLINE);
});
});
});
describe('[/users.getPresence]', () => {
it('should report the hider as offline to the viewer', async () => {
await request
.get(api('users.getPresence'))
.set(viewerCredentials)
.query({ userId: hider._id })
.expect(200)
.expect((res) => {
expect(res.body.presence).to.be.equal(UserStatus.OFFLINE);
});
});
it('should report the real presence to a user they do not hide from', async () => {
await request
.get(api('users.getPresence'))
.set(bystanderCredentials)
.query({ userId: hider._id })
.expect(200)
.expect((res) => {
expect(res.body.presence).to.be.equal(UserStatus.ONLINE);
});
});
});
describe('[/users.autocomplete]', () => {
const selector = (conditions?: object) => JSON.stringify({ term: hider.username, exceptions: [], ...(conditions && { conditions }) });
it('should list the hider as offline to the viewer', async () => {
await request
.get(api('users.autocomplete'))
.set(viewerCredentials)
.query({ selector: selector() })
.expect(200)
.expect((res) => {
expect(statusOf(res.body.items, hider.username)).to.be.equal(UserStatus.OFFLINE);
});
});
it('should list the real status to a user they do not hide from', async () => {
await request
.get(api('users.autocomplete'))
.set(bystanderCredentials)
.query({ selector: selector() })
.expect(200)
.expect((res) => {
expect(statusOf(res.body.items, hider.username)).to.be.equal(UserStatus.ONLINE);
});
});
it('should not return the hider when the viewer filters by online through the selector conditions', async () => {
await request
.get(api('users.autocomplete'))
.set(viewerCredentials)
.query({ selector: selector({ status: UserStatus.ONLINE }) })
.expect(200)
.expect((res) => {
expect(usernamesOf(res.body.items)).to.not.include(hider.username);
});
});
it('should return the hider through the selector conditions to a user they do not hide from', async () => {
await request
.get(api('users.autocomplete'))
.set(bystanderCredentials)
.query({ selector: selector({ status: UserStatus.ONLINE }) })
.expect(200)
.expect((res) => {
expect(usernamesOf(res.body.items)).to.include(hider.username);
});
});
});
describe('[/im.members]', () => {
let dm: IRoom;
before(async () => {
dm = (await createRoom({ type: 'd', username: hider.username, credentials: viewerCredentials })).body.room;
});
it('should return the hider when the viewer filters by offline', async () => {
await request
.get(api('im.members'))
.set(viewerCredentials)
.query({ 'roomId': dm._id, 'status[]': UserStatus.OFFLINE })
.expect(200)
.expect((res) => {
expect(usernamesOf(res.body.members)).to.include(hider.username);
expect(statusOf(res.body.members, hider.username)).to.be.equal(UserStatus.OFFLINE);
});
});
});
});