From 8ffd7d55bba87b3a6a66b1b8d67990bff97b80df Mon Sep 17 00:00:00 2001 From: Diego Mello Date: Mon, 24 Aug 2026 14:54:44 -0300 Subject: [PATCH 1/5] fix: stop request() from refusing pre-login REST calls (#385) --- interfaces/index.ts | 2 -- lib/api/RocketChat.ts | 26 +++++++++---------- ...th-guard.spec.ts => api.pre-login.spec.ts} | 22 +++++++--------- lib/api/__tests__/api.spec.ts | 12 ++++----- lib/api/__tests__/client.spec.ts | 8 +++--- lib/api/api.ts | 22 ++++++---------- lib/clients/__tests__/Rocketchat.spec.ts | 7 ++--- 7 files changed, 43 insertions(+), 56 deletions(-) rename lib/api/__tests__/{api.auth-guard.spec.ts => api.pre-login.spec.ts} (74%) diff --git a/interfaces/index.ts b/interfaces/index.ts index 2760ab6..ff2d755 100644 --- a/interfaces/index.ts +++ b/interfaces/index.ts @@ -188,14 +188,12 @@ export type ILoginCredentials = * Common args for POST, GET, PUT, DELETE requests * @param endpoint The API endpoint (including version) e.g. `chat.update` * @param data Payload for POST request to endpoint - * @param auth Require auth headers for endpoint, default true * @param ignore Allows certain matching error messages to not count as errors */ export interface IAPIRequest { ( endpoint: string, data?: any, - auth?: boolean, ignore?: RegExp, options?: any, apiVersion?: string diff --git a/lib/api/RocketChat.ts b/lib/api/RocketChat.ts index 67f788c..3cb3e0e 100644 --- a/lib/api/RocketChat.ts +++ b/lib/api/RocketChat.ts @@ -28,33 +28,33 @@ export default class ApiRocketChat extends ApiBase { online: (fields: any = userFields) => this.get('users.list', { fields, query: { 'status': { $ne: 'offline' } } }).then((r: any) => r.users), onlineNames: () => this.get('users.list', { fields: { 'username': 1 }, query: { 'status': { $ne: 'offline' } } }).then((r: any) => r.users.map((u: IUserAPI) => u.username)), onlineIds: () => this.get('users.list', { fields: { '_id': 1 }, query: { 'status': { $ne: 'offline' } } }).then((r: any) => r.users.map((u: IUserAPI) => u._id)), - info: async (username: string): Promise => (await this.get('users.info', { username }, true)).user + info: async (username: string): Promise => (await this.get('users.info', { username })).user } } get rooms (): any { return { - info: ({ rid }: any) => this.get('rooms.info', { rid }, true) + info: ({ rid }: any) => this.get('rooms.info', { rid }) } } // editMessage(message: IMessage) chat.update - joinRoom ({ rid }: any) { return this.post('channels.join', { roomId: rid }, true) } + joinRoom ({ rid }: any) { return this.post('channels.join', { roomId: rid }) } - async info () { return (await this.get('info', {}, this.loggedIn())).info } + async info () { return (await this.get('info', {})).info } /** * Send a prepared message object (with pre-defined room ID). * Usually prepared and called by sendMessageByRoomId or sendMessageByRoom. */ - async sendMessage (message: IMessage | string, rid: string): Promise { return (await this.post('chat.sendMessage', { message: this.prepareMessage(message, rid) }, true)).message } - getRoomIdByNameOrId (name: string): Promise { return this.get('chat.getRoomIdByNameOrId', { name }, true) } + async sendMessage (message: IMessage | string, rid: string): Promise { return (await this.post('chat.sendMessage', { message: this.prepareMessage(message, rid) })).message } + getRoomIdByNameOrId (name: string): Promise { return this.get('chat.getRoomIdByNameOrId', { name }) } getRoomNameById (rid: RID): Promise { return this.getRoomName(rid) } async getRoomName (rid: string): Promise { - const room = await this.get('chat.getRoomNameById', { rid }, true) + const room = await this.get('chat.getRoomNameById', { rid }) return room.name } - getRoomId (name: string) { return this.get('chat.find', { name }, true) } - async createDirectMessage (username: string) { return (await this.post('im.create', { username }, true)).room } + getRoomId (name: string) { return this.get('chat.find', { name }) } + async createDirectMessage (username: string) { return (await this.post('im.create', { username })).room } /** * Edit an existing message, replacing any attributes with those provided. @@ -68,7 +68,7 @@ export default class ApiRocketChat extends ApiBase { * @param emoji Accepts string like `:thumbsup:` to add 👍 reaction * @param messageId ID for a previously sent message */ - setReaction (emoji: string, messageId: string) { return this.post('chat.react', { emoji, messageId }, true) } + setReaction (emoji: string, messageId: string) { return this.post('chat.react', { emoji, messageId }) } // TODO fix this methods @@ -76,7 +76,7 @@ export default class ApiRocketChat extends ApiBase { updated: IMessage[], deleted: IMessage[] }> { - return (await this.get('chat.syncMessages', { roomId: rid, lastUpdate: lastUpdate.toISOString() }, true)).result + return (await this.get('chat.syncMessages', { roomId: rid, lastUpdate: lastUpdate.toISOString() })).result } /** Exit a room the bot has joined */ leaveRoom (rid: string): Promise { @@ -85,11 +85,11 @@ export default class ApiRocketChat extends ApiBase { /** Get information about a public group */ async channelInfo (query: { roomName?: string, roomId?: string }) { - return (await this.get('channels.info', query, true)).channel as Promise + return (await this.get('channels.info', query)).channel as Promise } /** Get information about a private group */ async privateInfo (query: { roomName?: string, roomId?: string }) { - return (await this.get('groups.info', query, true)).group as Promise + return (await this.get('groups.info', query)).group as Promise } } diff --git a/lib/api/__tests__/api.auth-guard.spec.ts b/lib/api/__tests__/api.pre-login.spec.ts similarity index 74% rename from lib/api/__tests__/api.auth-guard.spec.ts rename to lib/api/__tests__/api.pre-login.spec.ts index c34c747..4911d3a 100644 --- a/lib/api/__tests__/api.auth-guard.spec.ts +++ b/lib/api/__tests__/api.pre-login.spec.ts @@ -3,27 +3,25 @@ import { anonymousApiWithFakeClient, anonymousApiRocketChatWithFakeClient } from const infoResponse = () => ({ status: 200, data: { info: { version: '6.0.0' } } }) -describe('Api auth guard', () => { +describe('Api pre-login requests', () => { it('reports not logged in with no login', () => { const { api } = anonymousApiWithFakeClient() expect(api.loggedIn()).toBe(false) }) - it('refuses an authenticated request with no login', async () => { + it('sends a pre-login request to the client with no login', async () => { const { api, restClient } = anonymousApiWithFakeClient() - await expect(api.get('me', {})).rejects.toThrow(/requires a login/) - expect(restClient.requests).toHaveLength(0) - }) - - it('allows an unauthenticated request with no login', async () => { - const { api, restClient } = anonymousApiWithFakeClient() - - const pending = api.get('settings.public', {}, false) - restClient.lastRequest().resolve({ status: 200, data: { settings: [] } }) + const pending = api.post('users.forgotPassword', { email: 'user@example.com' }) + restClient.lastRequest().resolve({ status: 200, data: { success: true } }) - await expect(pending).resolves.toEqual({ settings: [] }) + await expect(pending).resolves.toEqual({ success: true }) + expect(restClient.requests).toHaveLength(1) + expect(restClient.lastRequest()).toMatchObject({ + endpoint: 'users.forgotPassword', + data: { email: 'user@example.com' } + }) }) it('logs in with no prior login', async () => { diff --git a/lib/api/__tests__/api.spec.ts b/lib/api/__tests__/api.spec.ts index 9748765..e815e20 100644 --- a/lib/api/__tests__/api.spec.ts +++ b/lib/api/__tests__/api.spec.ts @@ -85,7 +85,7 @@ describe('api', () => { const { api, restClient } = anonymousApiWithFakeClient() restClient.enqueueReply(emptySuccess()) - await api.post('login', { username: 'user' }, false) + await api.post('login', { username: 'user' }) expect(restClient.lastRequest()).toMatchObject({ method: 'POST', @@ -125,10 +125,10 @@ describe('api', () => { const { api, restClient } = await loggedInApiWithFakeClient() restClient.enqueueReply(emptySuccess(), emptySuccess(), emptySuccess(), emptySuccess()) - await api.get('chat.getMessage', {}, true, undefined, {}, 'v2') - await api.post('chat.postMessage', {}, true, undefined, {}, 'v2') - await api.put('chat.update', {}, true, undefined, {}, 'v2') - await api.del('chat.delete', {}, true, undefined, {}, 'v2') + await api.get('chat.getMessage', {}, undefined, {}, 'v2') + await api.post('chat.postMessage', {}, undefined, {}, 'v2') + await api.put('chat.update', {}, undefined, {}, 'v2') + await api.del('chat.delete', {}, undefined, {}, 'v2') expect(restClient.requests.map((request) => request.apiVersion)).toEqual([ 'v2', 'v2', 'v2', 'v2' @@ -169,7 +169,7 @@ describe('api', () => { const { api, restClient } = await loggedInApiWithFakeClient() restClient.enqueueReply({ status: 400, data: { error: 'nope' } }) - await expect(api.get('me', {}, true, /400/)).resolves.toEqual({ error: 'nope' }) + await expect(api.get('me', {}, /400/)).resolves.toEqual({ error: 'nope' }) }) it('throws when the restClient answers nothing at all', async () => { diff --git a/lib/api/__tests__/client.spec.ts b/lib/api/__tests__/client.spec.ts index ef17ea0..deb89b6 100644 --- a/lib/api/__tests__/client.spec.ts +++ b/lib/api/__tests__/client.spec.ts @@ -34,7 +34,7 @@ describe('REST client', () => { }) it('addresses the api version the caller asked for', async () => { - await api.get('rooms.info', {}, true, undefined, {}, 'v2') + await api.get('rooms.info', {}, undefined, {}, 'v2') expect(lastFetchCall().url).toBe('http://localhost:3000/api/v2/rooms.info?') }) @@ -99,7 +99,7 @@ describe('REST client', () => { it('drops the auth headers on a logout and keeps resolving the custom ones', async () => { jest.replaceProperty(settings, 'customHeaders', { 'X-Custom': 'first' }) await api.logout() - await api.get('settings.public', {}, false) + await api.get('settings.public', {}) expect(lastFetchCall().init.headers).toEqual({ 'Content-Type': 'application/json', @@ -107,13 +107,13 @@ describe('REST client', () => { }) jest.replaceProperty(settings, 'customHeaders', { 'X-Custom': 'second' }) - await api.get('settings.public', {}, false) + await api.get('settings.public', {}) expect(lastFetchCall().init.headers).toMatchObject({ 'X-Custom': 'second' }) }) it('sends only the headers the caller passed as options', async () => { - await api.get('me', {}, true, undefined, { customHeaders: { 'X-Only': 'this' } }) + await api.get('me', {}, undefined, { customHeaders: { 'X-Only': 'this' } }) expect(lastFetchCall().init.headers).toEqual({ 'X-Only': 'this' }) }) diff --git a/lib/api/api.ts b/lib/api/api.ts index e187eea..e10f056 100644 --- a/lib/api/api.ts +++ b/lib/api/api.ts @@ -48,7 +48,7 @@ import * as settings from '../settings'; // if (currentLogin.username === user.username) return currentLogin.result // else await logout() // } -// const result = (await this.post('login', user, false) as ILoginResultAPI) +// const result = (await this.post('login', user) as ILoginResultAPI) // if (result && result.data && result.data.authToken) { // currentLogin = { // result: result, // keep to return if login requested again for same user @@ -71,7 +71,7 @@ import * as settings from '../settings'; // return Promise.resolve() // } // this.logger.info(`[API] Logging out ${ currentLogin.username }`) -// return this.get('logout', null, true).then(() => { +// return this.get('logout', null).then(() => { // clearHeaders() // currentLogin = null // }) @@ -219,24 +219,18 @@ export default class Api extends SDKEventEmitter { * @param method Request method GET | POST | PUT | DEL * @param endpoint The API endpoint (including version) e.g. `chat.update` * @param data Payload for POST request to endpoint - * @param auth Require auth headers for endpoint, default true * @param ignore Allows certain matching error messages to not count as errors */ request = async ( method: 'POST' | 'GET' | 'PUT' | 'DELETE', endpoint: string, data: any = {}, - auth: boolean = true, ignore?: RegExp, options?: any, apiVersion: string = 'v1' ) => { this.logger?.debug(`[API] ${ method } ${ endpoint }: ${ JSON.stringify(data) }`) try { - if (auth && !this.loggedIn()) { - throw new Error(`API ${ method } ${ endpoint } requires a login`) - } - const { signal } = this.controller; options = { ...options, signal }; @@ -259,16 +253,16 @@ export default class Api extends SDKEventEmitter { } } /** Do a POST request to an API endpoint. */ - post: IAPIRequest = (endpoint, data, auth, ignore, options = {}, apiVersion) => this.request('POST', endpoint, data, auth, ignore, options, apiVersion) + post: IAPIRequest = (endpoint, data, ignore, options = {}, apiVersion) => this.request('POST', endpoint, data, ignore, options, apiVersion) /** Do a GET request to an API endpoint. */ - get: IAPIRequest = (endpoint, data, auth, ignore, options = {}, apiVersion) => this.request('GET', endpoint, data, auth, ignore, options, apiVersion) + get: IAPIRequest = (endpoint, data, ignore, options = {}, apiVersion) => this.request('GET', endpoint, data, ignore, options, apiVersion) /** Do a PUT request to an API endpoint. */ - put: IAPIRequest = (endpoint, data, auth, ignore, options = {}, apiVersion) => this.request('PUT', endpoint, data, auth, ignore, options, apiVersion) + put: IAPIRequest = (endpoint, data, ignore, options = {}, apiVersion) => this.request('PUT', endpoint, data, ignore, options, apiVersion) /** Do a DELETE request to an API endpoint. */ - del: IAPIRequest = (endpoint, data, auth, ignore, options = {}, apiVersion) => this.request('DELETE', endpoint, data, auth, ignore, options, apiVersion) + del: IAPIRequest = (endpoint, data, ignore, options = {}, apiVersion) => this.request('DELETE', endpoint, data, ignore, options, apiVersion) /** Abort all current API requests, leaving the next request free to run. */ abort = (): void => { @@ -287,7 +281,7 @@ export default class Api extends SDKEventEmitter { } async login (credentials: ILoginCredentials, args?: any): Promise { - const { data }: { data: ILoginData } = await this.post('login', { ...credentials, ...args }, false) + const { data }: { data: ILoginData } = await this.post('login', { ...credentials, ...args }) this.setLogin({ username: data.me.username ?? null, userId: data.userId, @@ -333,7 +327,7 @@ export default class Api extends SDKEventEmitter { if (!this.currentLogin) { return null } - const result = await this.post('logout', {}, true) + const result = await this.post('logout', {}) this.clearLogin() return result } diff --git a/lib/clients/__tests__/Rocketchat.spec.ts b/lib/clients/__tests__/Rocketchat.spec.ts index 1b5fe23..acccc0e 100644 --- a/lib/clients/__tests__/Rocketchat.spec.ts +++ b/lib/clients/__tests__/Rocketchat.spec.ts @@ -182,11 +182,8 @@ describe('client.logout', () => { expect((await loggedOutClient()).client.headers).not.toHaveProperty('X-Auth-Token') }) - it('leaves the guard refusing an authenticated request', async () => { - const client = await loggedOutClient() - - expect(client.loggedIn()).toBe(false) - await expect(client.get('me', {})).rejects.toThrow(/requires a login/) + it('reports itself logged out', async () => { + expect((await loggedOutClient()).loggedIn()).toBe(false) }) }) From ee6cb1261d04a0ceeb9966dca069fb7b751e5265 Mon Sep 17 00:00:00 2001 From: Diego Mello Date: Mon, 24 Aug 2026 15:04:45 -0300 Subject: [PATCH 2/5] refactor(test): name the no-Current-login specs after the domain --- ...re-login.spec.ts => api.no-current-login.spec.ts} | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) rename lib/api/__tests__/{api.pre-login.spec.ts => api.no-current-login.spec.ts} (82%) diff --git a/lib/api/__tests__/api.pre-login.spec.ts b/lib/api/__tests__/api.no-current-login.spec.ts similarity index 82% rename from lib/api/__tests__/api.pre-login.spec.ts rename to lib/api/__tests__/api.no-current-login.spec.ts index 4911d3a..e1d538c 100644 --- a/lib/api/__tests__/api.pre-login.spec.ts +++ b/lib/api/__tests__/api.no-current-login.spec.ts @@ -3,14 +3,14 @@ import { anonymousApiWithFakeClient, anonymousApiRocketChatWithFakeClient } from const infoResponse = () => ({ status: 200, data: { info: { version: '6.0.0' } } }) -describe('Api pre-login requests', () => { - it('reports not logged in with no login', () => { +describe('Api with no Current login', () => { + it('reports no Current login', () => { const { api } = anonymousApiWithFakeClient() expect(api.loggedIn()).toBe(false) }) - it('sends a pre-login request to the client with no login', async () => { + it('sends the Endpoint to the REST client with no Current login', async () => { const { api, restClient } = anonymousApiWithFakeClient() const pending = api.post('users.forgotPassword', { email: 'user@example.com' }) @@ -24,7 +24,7 @@ describe('Api pre-login requests', () => { }) }) - it('logs in with no prior login', async () => { + it('sets a Current login from a login with none held', async () => { const { api, restClient } = anonymousApiWithFakeClient() const pending = api.login({ username: 'user', password: 'pass' }) @@ -34,7 +34,7 @@ describe('Api pre-login requests', () => { expect(api.loggedIn()).toBe(true) }) - it('allows info() with no login', async () => { + it('sends info() with no Current login', async () => { const { api, restClient } = anonymousApiRocketChatWithFakeClient() const pending = api.info() @@ -43,7 +43,7 @@ describe('Api pre-login requests', () => { await expect(pending).resolves.toEqual({ version: '6.0.0' }) }) - it('sends info() authenticated once logged in', async () => { + it('sends info() with the auth headers once a Current login is held', async () => { const { api, restClient } = anonymousApiRocketChatWithFakeClient() const login = api.login({ username: 'user', password: 'pass' }) From 18f02e40f1a3f29f8ab0b0f1e84e1d34ebf86e26 Mon Sep 17 00:00:00 2001 From: Diego Mello Date: Mon, 24 Aug 2026 15:09:40 -0300 Subject: [PATCH 3/5] refactor: drop the dead commented login/logout block from api.ts Restores the logged-out client's assertion that an Endpoint still reaches the REST client. --- lib/api/api.ts | 64 ------------------------ lib/clients/__tests__/Rocketchat.spec.ts | 8 +++ 2 files changed, 8 insertions(+), 64 deletions(-) diff --git a/lib/api/api.ts b/lib/api/api.ts index e10f056..35c93b6 100644 --- a/lib/api/api.ts +++ b/lib/api/api.ts @@ -13,70 +13,6 @@ import { import { SDKEventEmitter } from '../emitter' import * as settings from '../settings'; -/** Check for existing login */ -// export function loggedIn () { -// return (currentLogin !== null) -// } - -/** - * Prepend protocol (or put back if removed from env settings for driver) - * Hard code endpoint prefix, because all syntax depends on this version - */ -// export const url = `${(host.indexOf('http') === -1) ? host.replace(/^(\/\/)?/, 'http://') : host}/api/v1/` - -/** Populate auth headers (from response data on login) */ -// export function setAuth (authData: {authToken: string, userId: string}) { -// client.defaults.headers.common['X-Auth-Token'] = authData.authToken -// client.defaults.headers.common['X-User-Id'] = authData.userId -// } - -// /** Clear headers so they can't be used without logging in again */ -// export function clearHeaders () { -// delete client.defaults.headers.common['X-Auth-Token'] -// delete client.defaults.headers.common['X-User-Id'] -// } - -// /** -// * Login a user for further API calls -// * Result should come back with a token, to authorise following requests. -// * Use env default credentials, unless overridden by login arguments. -// */ -// export async function login (user: ICredentialsAPI = { username, password }) { -// this.logger.info(`[API] Logging in ${user.username}`) -// if (currentLogin !== null) { -// this.logger.debug(`[API] Already logged in`) -// if (currentLogin.username === user.username) return currentLogin.result -// else await logout() -// } -// const result = (await this.post('login', user) as ILoginResultAPI) -// if (result && result.data && result.data.authToken) { -// currentLogin = { -// result: result, // keep to return if login requested again for same user -// username: user.username, // keep to compare with following login attempt -// authToken: result.data.authToken, -// userId: result.data.userId -// } -// setAuth(currentLogin) -// this.logger.info(`[API] Logged in ID ${currentLogin.userId}`) -// return result -// } else { -// throw new Error(`[API] Login failed for ${user.username}`) -// } -// } - -// /** Logout a user at end of API calls */ -// export function logout () { -// if (currentLogin === null) { -// this.logger.debug(`[API] Already logged out`) -// return Promise.resolve() -// } -// this.logger.info(`[API] Logging out ${ currentLogin.username }`) -// return this.get('logout', null).then(() => { -// clearHeaders() -// currentLogin = null -// }) -// } - export interface IClient { host: string headers: any diff --git a/lib/clients/__tests__/Rocketchat.spec.ts b/lib/clients/__tests__/Rocketchat.spec.ts index acccc0e..d8fed3b 100644 --- a/lib/clients/__tests__/Rocketchat.spec.ts +++ b/lib/clients/__tests__/Rocketchat.spec.ts @@ -185,6 +185,14 @@ describe('client.logout', () => { it('reports itself logged out', async () => { expect((await loggedOutClient()).loggedIn()).toBe(false) }) + + it('still sends an Endpoint to the REST client', async () => { + const client = await loggedOutClient() + + client.get('me', {}) + + expect(client.client.lastRequest().endpoint).toBe('me') + }) }) describe('client.logger', () => { From 874d4b1d83fdacae8cf50dbee609c3ac17e2f4cd Mon Sep 17 00:00:00 2001 From: Diego Mello Date: Mon, 24 Aug 2026 15:11:16 -0300 Subject: [PATCH 4/5] fix(test): hand the logged-out helper's REST client back to its specs --- lib/clients/__tests__/Rocketchat.spec.ts | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/lib/clients/__tests__/Rocketchat.spec.ts b/lib/clients/__tests__/Rocketchat.spec.ts index d8fed3b..6e040c4 100644 --- a/lib/clients/__tests__/Rocketchat.spec.ts +++ b/lib/clients/__tests__/Rocketchat.spec.ts @@ -175,23 +175,25 @@ describe('client.logout', () => { restClient.lastRequest().resolve({ status: 200, data: {} }) await pending - return client + return { client, restClient } } it('clears the REST auth headers', async () => { - expect((await loggedOutClient()).client.headers).not.toHaveProperty('X-Auth-Token') + expect((await loggedOutClient()).client.client.headers).not.toHaveProperty('X-Auth-Token') }) it('reports itself logged out', async () => { - expect((await loggedOutClient()).loggedIn()).toBe(false) + expect((await loggedOutClient()).client.loggedIn()).toBe(false) }) it('still sends an Endpoint to the REST client', async () => { - const client = await loggedOutClient() + const { client, restClient } = await loggedOutClient() - client.get('me', {}) + const pending = client.get('me', {}) + restClient.lastRequest().resolve({ status: 200, data: { success: true } }) + await pending - expect(client.client.lastRequest().endpoint).toBe('me') + expect(restClient.lastRequest().endpoint).toBe('me') }) }) From 3a7b096c53e4f8ca37a316fdd6bc0c8587699dc5 Mon Sep 17 00:00:00 2001 From: Diego Mello Date: Mon, 24 Aug 2026 15:12:07 -0300 Subject: [PATCH 5/5] refactor(test): destructure the logged-out helper uniformly --- lib/clients/__tests__/Rocketchat.spec.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/clients/__tests__/Rocketchat.spec.ts b/lib/clients/__tests__/Rocketchat.spec.ts index 6e040c4..63e4c98 100644 --- a/lib/clients/__tests__/Rocketchat.spec.ts +++ b/lib/clients/__tests__/Rocketchat.spec.ts @@ -179,11 +179,15 @@ describe('client.logout', () => { } it('clears the REST auth headers', async () => { - expect((await loggedOutClient()).client.client.headers).not.toHaveProperty('X-Auth-Token') + const { client } = await loggedOutClient() + + expect(client.client.headers).not.toHaveProperty('X-Auth-Token') }) it('reports itself logged out', async () => { - expect((await loggedOutClient()).client.loggedIn()).toBe(false) + const { client } = await loggedOutClient() + + expect(client.loggedIn()).toBe(false) }) it('still sends an Endpoint to the REST client', async () => {